001package jmri.jmrit.display;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006import java.util.stream.Collectors;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.Nonnull;
010import jmri.InstanceManager;
011import jmri.InstanceManagerAutoDefault;
012import jmri.beans.Bean;
013
014/**
015 * Manager for JMRI Editors. This manager tracks editors, extending the Set
016 * interface to do so (so it can be interacted with as a normal set), while also
017 * providing some methods specific to editors.
018 * <p>
019 * This manager listens to the {@code title} property of Editors to be notified
020 * to changes to the title of the Editor that could affect the order of editors.
021 * <p>
022 * This manager generates an {@link java.beans.IndexedPropertyChangeEvent} for
023 * the property named {@code editors} when an editor is added or removed and
024 * forwards the {@link java.beans.PropertyChangeEvent} for the {@code title}
025 * property of Editors in the manager.
026 *
027 * @author Randall Wood Copyright 2020
028 */
029public class EditorManager extends Bean implements PropertyChangeListener, InstanceManagerAutoDefault {
030
031    public static final String EDITORS = "editors";
032    public static final String TITLE = "title";
033    private final SortedSet<Editor> set = Collections.synchronizedSortedSet(new TreeSet<>(Comparator.comparing(Editor::getTitle)));
034
035    public EditorManager() {
036        super(false);
037    }
038
039    /**
040     * Set the title for the Preferences / Messages tab.
041     * Called by JmriUserPreferencesManager.
042     * @return the title string.
043     */
044    public String getClassDescription() {
045        return Bundle.getMessage("TitlePanelDialogs");  // NOI18N
046    }
047
048    /**
049     * Set the details for Preferences / Messages tab.
050     * Called by JmriUserPreferencesManager.
051     * <p>
052     * The dialogs are in jmri.configurexml.LoadXmlConfigAction and jmri.jmrit.display.Editor.
053     * They are anchored here since the preferences system appears to need a class that can instantiated.
054     */
055    public void setMessagePreferencesDetails() {
056        InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(
057                "jmri.jmrit.display.EditorManager", "skipHideDialog", Bundle.getMessage("PanelHideSkip"));  // NOI18N
058        InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(
059                "jmri.jmrit.display.EditorManager", "skipDupLoadDialog", Bundle.getMessage("DuplicateLoadSkip"));  // NOI18N
060    }
061
062    /**
063     * Add an editor to this manager.
064     *
065     * @param editor the editor to add
066     */
067    public void add(@Nonnull Editor editor) {
068        boolean result = set.add(editor);
069        if (result) {
070            fireIndexedPropertyChange(EDITORS, set.size(), null, editor);
071            editor.addPropertyChangeListener(TITLE, this);
072        }
073    }
074
075    /**
076     * Check if an editor is in the manager.
077     *
078     * @param editor the editor to check for
079     * @return true if this manager contains an editor with name; false
080     * otherwise
081     */
082    public boolean contains(@Nonnull Editor editor) {
083        return set.contains(editor);
084    }
085
086    /**
087     * Get all managed editors. This set is sorted by the title of the editor.
088     *
089     * @return the set of all editors
090     */
091    @Nonnull
092    public SortedSet<Editor> getAll() {
093        return new TreeSet<>(set);
094    }
095
096    /**
097     * Get all managed editors that implement the specified type. This set is
098     * sorted by the title of the editor.
099     *
100     * @param <T> the specified type
101     * @param type the specified type
102     * @return the set of all editors of the specified type
103     */
104    @Nonnull
105    public <T extends Editor> SortedSet<T> getAll(@Nonnull Class<T> type) {
106        return set.stream()
107                .filter(e -> type.isAssignableFrom(e.getClass()))
108                .map(type::cast)
109                .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Editor::getTitle))));
110    }
111
112    /**
113     * Get the editor with the given title.
114     *
115     * @param title the title of the editor
116     * @return the editor with the given title or null if no editor by that title
117     * exists
118     */
119    @CheckForNull
120    public Editor get(@Nonnull String title) {
121        return getAll().stream().filter(e -> e.getTitle().equals(title)).findFirst().orElse(null);
122    }
123
124    /**
125     * Get the editor with the given name.
126     *
127     * @param name the name of the editor
128     * @return the editor with the given name or null if no editor by that name
129     * exists
130     */
131    @CheckForNull
132    public Editor getByName(@Nonnull String name) {
133        return getAll().stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
134    }
135
136    /**
137     * Get the editor with the given name or the editor with the given target frame name.
138     *
139     * @param name the name of the editor or target frame
140     * @return the editor or null
141     */
142    @CheckForNull
143    public Editor getTargetFrame(@Nonnull String name) {
144        Editor editor = get(name);
145        if (editor != null) {
146            return editor;
147        }
148        return getAll().stream().filter(e -> e.getTargetFrame().getTitle().equals(name)).findFirst().orElse(null);
149    }
150
151    /**
152     * Get the editor with the given name and type.
153     *
154     * @param <T> the type of the editor
155     * @param type the type of the editor
156     * @param name the name of the editor
157     * @return the editor with the given name or null if no editor by that name
158     * exists
159     */
160    @CheckForNull
161    public <T extends Editor> T get(@Nonnull Class<T> type, @Nonnull String name) {
162        return type.cast(set.stream()
163                .filter(e -> e.getClass().isAssignableFrom(type) && e.getTitle().equals(name))
164                .findFirst().orElse(null));
165    }
166
167    /**
168     * Remove an editor from this manager.
169     *
170     * @param editor the editor to remove
171     */
172    public void remove(@Nonnull Editor editor) {
173        boolean result = set.remove(editor);
174        if (result) {
175            fireIndexedPropertyChange(EDITORS, set.size(), editor, null);
176            editor.removePropertyChangeListener(TITLE, this);
177        }
178    }
179
180    @Override
181    public void propertyChange(PropertyChangeEvent evt) {
182        if (evt.getSource() instanceof Editor) {
183            Editor editor = (Editor) evt.getSource();
184            if (contains(editor) && TITLE.equals(evt.getPropertyName())) {
185                set.remove(editor);
186                set.add(editor);
187                firePropertyChange(evt);
188            }
189        }
190    }
191
192    /**
193     * Check if an editor with the specified name is in the manager.
194     *
195     * @param name the name to check for
196     * @return true if this manager contains an editor with name; false
197     * otherwise
198     */
199    public boolean contains(String name) {
200        return get(name) != null;
201    }
202
203    /**
204     * Get the set of all Editors as a List. This is a convenience method for
205     * use in scripts.
206     *
207     * @return the set of all Editors
208     */
209    @Nonnull
210    public List<Editor> getList() {
211        return new ArrayList<>(getAll());
212    }
213
214    /**
215     * Get the set of all editors that implement the specified type. This is a
216     * convenience method for use in scripts.
217     *
218     * @param <T> the specified type
219     * @param type the specified type
220     * @return the set of all editors that implement the specified type
221     */
222    @Nonnull
223    public <T extends Editor> List<T> getList(@Nonnull Class<T> type) {
224        return new ArrayList<>(getAll(type));
225    }
226}