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}