001package jmri;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.ArrayList;
006import java.util.Objects;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.CheckReturnValue;
010import javax.annotation.Nonnull;
011
012import jmri.managers.AbstractManager;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * Instance for controlling the issuing of NamedBeanHandles.
019 * <hr>
020 * The NamedBeanHandleManager, deals with controlling and updating {@link NamedBean} objects
021 * across JMRI. When a piece of code requires persistent access to a bean, it
022 * should use a {@link NamedBeanHandle}. The {@link NamedBeanHandle} stores not only the bean
023 * that has been requested but also the named that was used to request it
024 * (either User or System Name).
025 * <p>
026 * This Manager will only issue out one {@link NamedBeanHandle} per Bean/Name request.
027 * The Manager also deals with updates and changes to the names of {@link NamedBean} objects, along
028 * with moving usernames between different beans.
029 * <p>
030 * If a beans username is changed by the user, then the name will be updated in
031 * the NamedBeanHandle. If a username is moved from one bean to another, then
032 * the bean reference will be updated and the propertyChangeListener attached to
033 * that bean will also be moved, so long as the correct method of adding the
034 * listener has been used.
035 * <hr>
036 * This file is part of JMRI.
037 * <p>
038 * JMRI is free software; you can redistribute it and/or modify it under the
039 * terms of version 2 of the GNU General Public License as published by the Free
040 * Software Foundation. See the "COPYING" file for a copy of this license.
041 * <p>
042 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
043 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
044 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
045 *
046 * @see jmri.NamedBean
047 * @see jmri.NamedBeanHandle
048 *
049 * @author Kevin Dickerson Copyright (C) 2011
050 */
051public class NamedBeanHandleManager extends AbstractManager<NamedBean> implements InstanceManagerAutoDefault {
052
053    public NamedBeanHandleManager() {
054        // use Internal memo as connection for this manager
055        super();
056    }
057
058    @SuppressWarnings("unchecked") // namedBeanHandles contains multiple types of NameBeanHandles<T>
059    @Nonnull
060    @CheckReturnValue
061    public <T extends NamedBean> NamedBeanHandle<T> getNamedBeanHandle(@Nonnull String name, @Nonnull T bean) {
062        Objects.requireNonNull(bean, "bean must be nonnull");
063        Objects.requireNonNull(name, "name must be nonnull");
064        if (name.isEmpty()) {
065            throw new IllegalArgumentException("name cannot be empty in getNamedBeanHandle");
066        }
067        NamedBeanHandle<T> temp = new NamedBeanHandle<>(name, bean);
068        for (NamedBeanHandle<T> h : namedBeanHandles) {
069            if (temp.equals(h)) {
070                return h;
071            }
072        }
073        namedBeanHandles.add(temp);
074        return temp;
075    }
076
077    /**
078     * Update the name of a bean in its references.
079     * <p>
080     * <strong>Note</strong> this does not change the name on the bean, it only
081     * changes the references.
082     *
083     * @param <T>     the type of the bean
084     * @param oldName the name changing from
085     * @param newName the name changing to
086     * @param bean    the bean being renamed
087     */
088    @SuppressWarnings("unchecked") // namedBeanHandles contains multiple types of NameBeanHandles<T>
089    public <T extends NamedBean> void renameBean(@Nonnull String oldName, @Nonnull String newName, @Nonnull T bean) {
090
091        /*Gather a list of the beans in the system with the oldName ref.
092         Although when we get a new bean we always return the first one that exists,
093         when a rename is performed it doesn't delete the bean with the old name;
094         it simply updates the name to the new one. So hence you can end up with
095         multiple named bean entries for one name.
096         */
097        NamedBeanHandle<T> oldBean = new NamedBeanHandle<>(oldName, bean);
098        for (NamedBeanHandle<T> h : namedBeanHandles) {
099            if (oldBean.equals(h)) {
100                h.setName(newName);
101            }
102        }
103        updateListenerRef(oldName, newName, bean);
104    }
105
106    /**
107     * Effectively move a name from one bean to another.
108     * <p>
109     * <strong>Note</strong> only updates the references to point to the new
110     * bean; does not move the name provided from one bean to another.
111     *
112     * @param <T>     the bean type
113     * @param oldBean bean loosing the name
114     * @param name    name being moved
115     * @param newBean bean gaining the name
116     */
117    //Checks are performed to make sure that the beans are the same type before being moved
118    @SuppressWarnings("unchecked") // namedBeanHandles contains multiple types of NameBeanHandles<T>
119    public <T extends NamedBean> void moveBean(@Nonnull T oldBean, @Nonnull T newBean, @Nonnull String name) {
120        /*Gather a list of the beans in the system with the oldBean ref.
121         Although when a new bean is requested, we always return the first one that exists
122         when a move is performed it doesn't delete the namedbeanhandle with the oldBean
123         it simply updates the bean to the new one. So hence you can end up with
124         multiple bean entries with the same name.
125         */
126
127        NamedBeanHandle<T> oldNamedBean = new NamedBeanHandle<>(name, oldBean);
128        for (NamedBeanHandle<T> h : namedBeanHandles) {
129            if (oldNamedBean.equals(h)) {
130                h.setBean(newBean);
131            }
132        }
133        moveListener(oldBean, newBean, name);
134    }
135
136    public void updateBeanFromUserToSystem(@Nonnull NamedBean bean) {
137        String systemName = bean.getSystemName();
138        String userName = bean.getUserName();
139        if (userName == null) {
140            log.warn("updateBeanFromUserToSystem requires non-blank user name: \"{}\" not renamed", systemName);
141            return;
142        }
143        renameBean(userName, systemName, bean);
144    }
145
146    public void updateBeanFromSystemToUser(@Nonnull NamedBean bean) throws JmriException {
147        String userName = bean.getUserName();
148        String systemName = bean.getSystemName();
149
150        if ((userName == null) || (userName.equals(""))) {
151            log.error("UserName is empty, can not update items to use UserName");
152            throw new JmriException("UserName is empty, can not update items to use UserName");
153        }
154        renameBean(systemName, userName, bean);
155    }
156
157    @CheckReturnValue
158    public <T extends NamedBean> boolean inUse(@Nonnull String name, @Nonnull T bean) {
159        NamedBeanHandle<T> temp = new NamedBeanHandle<>(name, bean);
160        return namedBeanHandles.stream().anyMatch((h) -> (temp.equals(h)));
161    }
162
163    @CheckForNull
164    @CheckReturnValue
165    public <T extends NamedBean> NamedBeanHandle<T> newNamedBeanHandle(@Nonnull String name, @Nonnull T bean, @Nonnull Class<T> type) {
166        return getNamedBeanHandle(name, bean);
167    }
168
169    /**
170     * A method to update the listener reference from oldName to a newName
171     */
172    private void updateListenerRef(@Nonnull String oldName, @Nonnull String newName, @Nonnull NamedBean nBean) {
173        java.beans.PropertyChangeListener[] listeners = nBean.getPropertyChangeListenersByReference(oldName);
174        for (java.beans.PropertyChangeListener listener : listeners) {
175            nBean.updateListenerRef(listener, newName);
176        }
177    }
178
179    /**
180     * Moves a propertyChangeListener from one bean to another, where the
181     * listener reference matches the currentName.
182     */
183    private void moveListener(@Nonnull NamedBean oldBean, @Nonnull NamedBean newBean, @Nonnull String currentName) {
184        java.beans.PropertyChangeListener[] listeners = oldBean.getPropertyChangeListenersByReference(currentName);
185        for (java.beans.PropertyChangeListener l : listeners) {
186            String listenerRef = oldBean.getListenerRef(l);
187            oldBean.removePropertyChangeListener(l);
188            newBean.addPropertyChangeListener(l, currentName, listenerRef);
189        }
190    }
191
192    @Override
193    public void dispose() {
194        super.dispose();
195    }
196
197    @SuppressWarnings("rawtypes") // namedBeanHandles contains multiple types of NameBeanHandles<T>
198    ArrayList<NamedBeanHandle> namedBeanHandles = new ArrayList<>();
199
200    /**
201     * Don't want to store this information
202     */
203    @Override
204    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
205            justification = "This method intentionally doesn't do anything")
206    protected void registerSelf() {
207    }
208
209    @Override
210    @CheckReturnValue
211    public char typeLetter() {
212        throw new UnsupportedOperationException("Not supported yet.");
213    }
214
215    @Override
216    @Nonnull
217    @CheckReturnValue
218    public String makeSystemName(@Nonnull String s) {
219        throw new UnsupportedOperationException("Not supported yet.");
220    }
221
222    @Override
223    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
224            justification = "This method must never be called")
225    public void register(@Nonnull NamedBean n) {
226        throw new UnsupportedOperationException("Not supported yet.");
227    }
228
229    @Override
230    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
231            justification = "This method must never be called")
232    public void deregister(@Nonnull NamedBean n) {
233        throw new UnsupportedOperationException("Not supported yet.");
234    }
235
236    @Override
237    @CheckReturnValue
238    public int getXMLOrder() {
239        throw new UnsupportedOperationException("Not supported yet.");
240    }
241
242    @Override
243    @Nonnull
244    @CheckReturnValue
245    public String getBeanTypeHandled(boolean plural) {
246        return Bundle.getMessage(plural ? "BeanNames" : "BeanName");
247    }
248
249    /**
250     * {@inheritDoc}
251     */
252    @Override
253    public Class<NamedBean> getNamedBeanClass() {
254        return NamedBean.class;
255    }
256
257    private final static Logger log = LoggerFactory.getLogger(NamedBeanHandleManager.class);
258
259}