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