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}