001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 */
035public final 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 treeTable TreeTable to perform updates on.
049     */
050    /* package */ ListToTreeSelectionModelWrapper(TreeTable treeTable) {
051        this.treeTable = treeTable;
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 accordingly.
060     *
061     * @return the list selection model
062     */
063    public ListSelectionModel getListSelectionModel() {
064        return listSelectionModel;
065    }
066
067    /**
068     * This is overridden to set {@code updatingListSelectionModel}
069     * and message super. This is the only place DefaultTreeSelectionModel
070     * alters the ListSelectionModel.
071     */
072    @Override
073    public void resetRowSelection() {
074        if (!updatingListSelectionModel) {
075            updatingListSelectionModel = true;
076            try {
077                super.resetRowSelection();
078            }
079            finally {
080                updatingListSelectionModel = false;
081            }
082        }
083        // Notice how we don't message super if
084        // updatingListSelectionModel is true. If
085        // updatingListSelectionModel is true, it implies the
086        // ListSelectionModel has already been updated and the
087        // paths are the only thing that needs to be updated.
088    }
089
090    /**
091     * If {@code updatingListSelectionModel} is false, this will
092     * reset the selected paths from the selected rows in the list selection model.
093     */
094    private void updateSelectedPathsFromSelectedRows() {
095        if (!updatingListSelectionModel) {
096            updatingListSelectionModel = true;
097            try {
098                // This is way expensive, ListSelectionModel needs an
099                // enumerator for iterating.
100                final int min = listSelectionModel.getMinSelectionIndex();
101                final int max = listSelectionModel.getMaxSelectionIndex();
102
103                clearSelection();
104                if (min != -1 && max != -1) {
105                    for (int counter = min; counter <= max; counter++) {
106                        updateSelectedPathIfRowIsSelected(counter);
107                    }
108                }
109            }
110            finally {
111                updatingListSelectionModel = false;
112            }
113        }
114    }
115
116    /**
117     * If the row at given index is selected, selected paths are updated.
118     *
119     * @param counter number of row.
120     */
121    private void updateSelectedPathIfRowIsSelected(int counter) {
122        if (listSelectionModel.isSelectedIndex(counter)) {
123            final TreePath selPath = treeTable.getTree().getPathForRow(counter);
124
125            if (selPath != null) {
126                addSelectionPath(selPath);
127            }
128        }
129    }
130
131}