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        InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(
061                "jmri.jmrit.display.EditorManager", "skipEntryExitDialog", Bundle.getMessage("EntryExitBlockSkip"));  // NOI18N
062    }
063
064    /**
065     * Add an editor to this manager.
066     *
067     * @param editor the editor to add
068     */
069    public void add(@Nonnull Editor editor) {
070        boolean result = set.add(editor);
071        if (result) {
072            fireIndexedPropertyChange(EDITORS, set.size(), null, editor);
073            editor.addPropertyChangeListener(TITLE, this);
074        }
075    }
076
077    /**
078     * Check if an editor is in the manager.
079     *
080     * @param editor the editor to check for
081     * @return true if this manager contains an editor with name; false
082     * otherwise
083     */
084    public boolean contains(@Nonnull Editor editor) {
085        return set.contains(editor);
086    }
087
088    /**
089     * Get all managed editors. This set is sorted by the title of the editor.
090     *
091     * @return the set of all editors
092     */
093    @Nonnull
094    public SortedSet<Editor> getAll() {
095        return new TreeSet<>(set);
096    }
097
098    /**
099     * Get all managed editors that implement the specified type. This set is
100     * sorted by the title of the editor.
101     *
102     * @param <T> the specified type
103     * @param type the specified type
104     * @return the set of all editors of the specified type
105     */
106    @Nonnull
107    public <T extends Editor> SortedSet<T> getAll(@Nonnull Class<T> type) {
108        return set.stream()
109                .filter(e -> type.isAssignableFrom(e.getClass()))
110                .map(type::cast)
111                .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Editor::getTitle))));
112    }
113
114    /**
115     * Get the editor with the given title.
116     *
117     * @param title the title of the editor
118     * @return the editor with the given title or null if no editor by that title
119     * exists
120     */
121    @CheckForNull
122    public Editor get(@Nonnull String title) {
123        return getAll().stream().filter(e -> e.getTitle().equals(title)).findFirst().orElse(null);
124    }
125
126    /**
127     * Get the editor with the given name.
128     *
129     * @param name the name of the editor
130     * @return the editor with the given name or null if no editor by that name
131     * exists
132     */
133    @CheckForNull
134    public Editor getByName(@Nonnull String name) {
135        return getAll().stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
136    }
137
138    /**
139     * Get the editor with the given name or the editor with the given target frame name.
140     *
141     * @param name the name of the editor or target frame
142     * @return the editor or null
143     */
144    @CheckForNull
145    public Editor getTargetFrame(@Nonnull String name) {
146        Editor editor = get(name);
147        if (editor != null) {
148            return editor;
149        }
150        return getAll().stream().filter(e -> e.getTargetFrame().getTitle().equals(name)).findFirst().orElse(null);
151    }
152
153    /**
154     * Get the editor with the given name and type.
155     *
156     * @param <T> the type of the editor
157     * @param type the type of the editor
158     * @param name the name of the editor
159     * @return the editor with the given name or null if no editor by that name
160     * exists
161     */
162    @CheckForNull
163    public <T extends Editor> T get(@Nonnull Class<T> type, @Nonnull String name) {
164        return type.cast(set.stream()
165                .filter(e -> e.getClass().isAssignableFrom(type) && e.getTitle().equals(name))
166                .findFirst().orElse(null));
167    }
168
169    /**
170     * Remove an editor from this manager.
171     *
172     * @param editor the editor to remove
173     */
174    public void remove(@Nonnull Editor editor) {
175        boolean result = set.remove(editor);
176        if (result) {
177            fireIndexedPropertyChange(EDITORS, set.size(), editor, null);
178            editor.removePropertyChangeListener(TITLE, this);
179        }
180    }
181
182    @Override
183    public void propertyChange(PropertyChangeEvent evt) {
184        if (evt.getSource() instanceof Editor) {
185            Editor editor = (Editor) evt.getSource();
186            if (contains(editor) && TITLE.equals(evt.getPropertyName())) {
187                set.remove(editor);
188                set.add(editor);
189                firePropertyChange(evt);
190            }
191        }
192    }
193
194    /**
195     * Check if an editor with the specified name is in the manager.
196     *
197     * @param name the name to check for
198     * @return true if this manager contains an editor with name; false
199     * otherwise
200     */
201    public boolean contains(String name) {
202        return get(name) != null;
203    }
204
205    /**
206     * Get the set of all Editors as a List. This is a convenience method for
207     * use in scripts.
208     *
209     * @return the set of all Editors
210     */
211    @Nonnull
212    public List<Editor> getList() {
213        return new ArrayList<>(getAll());
214    }
215
216    /**
217     * Get the set of all editors that implement the specified type. This is a
218     * convenience method for use in scripts.
219     *
220     * @param <T> the specified type
221     * @param type the specified type
222     * @return the set of all editors that implement the specified type
223     */
224    @Nonnull
225    public <T extends Editor> List<T> getList(@Nonnull Class<T> type) {
226        return new ArrayList<>(getAll(type));
227    }
228}