001package jmri.jmrit.logixng.actions; 002 003import jmri.jmrit.logixng.NamedBeanType; 004 005import java.beans.*; 006import java.util.*; 007import java.util.stream.Collectors; 008 009import jmri.*; 010import jmri.jmrit.logixng.*; 011import jmri.jmrit.logixng.util.DuplicateKeyMap; 012 013import net.jcip.annotations.GuardedBy; 014 015/** 016 * This action listens on some beans and runs the ConditionalNG on property change. 017 * 018 * @author Daniel Bergqvist Copyright 2019 019 */ 020public class ActionListenOnBeans extends AbstractDigitalAction 021 implements PropertyChangeListener, VetoableChangeListener { 022 023 private final Map<String, NamedBeanReference> _namedBeanReferences = new DuplicateKeyMap<>(); 024 private String _localVariableNamedBean; 025 private String _localVariableEvent; 026 private String _localVariableNewValue; 027 028 @GuardedBy("this") 029 private final Deque<PropertyChangeEvent> _eventQueue = new ArrayDeque<>(); 030 031 032 public ActionListenOnBeans(String sys, String user) 033 throws BadUserNameException, BadSystemNameException { 034 super(sys, user); 035 } 036 037 @Override 038 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) { 039 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 040 String sysName = systemNames.get(getSystemName()); 041 String userName = userNames.get(getSystemName()); 042 if (sysName == null) sysName = manager.getAutoSystemName(); 043 ActionListenOnBeans copy = new ActionListenOnBeans(sysName, userName); 044 copy.setComment(getComment()); 045 copy.setLocalVariableNamedBean(_localVariableNamedBean); 046 copy.setLocalVariableEvent(_localVariableEvent); 047 copy.setLocalVariableNewValue(_localVariableNewValue); 048 for (NamedBeanReference reference : _namedBeanReferences.values()) { 049 copy.addReference(reference); 050 } 051 return manager.registerAction(copy); 052 } 053 054 /** 055 * Register a bean 056 * The bean must be on the form "beantype:name" where beantype is for 057 * example turnout, sensor or memory, and name is the name of the bean. 058 * The type can be upper case or lower case, it doesn't matter. 059 * @param beanAndType the bean and type 060 */ 061 public void addReference(String beanAndType) { 062 assertListenersAreNotRegistered(log, "addReference"); 063 String[] parts = beanAndType.split(":"); 064 if ((parts.length < 2) || (parts.length > 3)) { 065 throw new IllegalArgumentException( 066 "Parameter 'beanAndType' must be on the format type:name" 067 + " where type is turnout, sensor, memory, ..., or on the" 068 + " format type:name:all where all is yes or no"); 069 } 070 071 boolean listenToAll = false; 072 if (parts.length == 3) listenToAll = "yes".equals(parts[2]); // NOI18N 073 074 try { 075 NamedBeanType type = NamedBeanType.valueOf(parts[0]); 076 NamedBeanReference reference = new NamedBeanReference(parts[1], type, listenToAll); 077 addReference(reference); 078 } catch (IllegalArgumentException e) { 079 String types = Arrays.asList(NamedBeanType.values()) 080 .stream() 081 .map(Enum::toString) 082 .collect(Collectors.joining(", ")); 083 throw new IllegalArgumentException( 084 "Parameter 'beanAndType' has wrong type. Valid types are: " + types); 085 } 086 } 087 088 public void addReference(NamedBeanReference reference) { 089 assertListenersAreNotRegistered(log, "addReference"); 090 _namedBeanReferences.put(reference._name, reference); 091 reference._type.getManager().addVetoableChangeListener(this); 092 } 093 094 public void removeReference(NamedBeanReference reference) { 095 assertListenersAreNotRegistered(log, "removeReference"); 096 _namedBeanReferences.remove(reference._name, reference); 097 reference._type.getManager().removeVetoableChangeListener(this); 098 } 099 100 public Collection<NamedBeanReference> getReferences() { 101 return _namedBeanReferences.values(); 102 } 103 104 public void clearReferences() { 105 _namedBeanReferences.clear(); 106 } 107 108 public void setLocalVariableNamedBean(String localVariableNamedBean) { 109 if ((localVariableNamedBean != null) && (!localVariableNamedBean.isEmpty())) { 110 this._localVariableNamedBean = localVariableNamedBean; 111 } else { 112 this._localVariableNamedBean = null; 113 } 114 } 115 116 public String getLocalVariableNamedBean() { 117 return _localVariableNamedBean; 118 } 119 120 public void setLocalVariableEvent(String localVariableEvent) { 121 if ((localVariableEvent != null) && (!localVariableEvent.isEmpty())) { 122 this._localVariableEvent = localVariableEvent; 123 } else { 124 this._localVariableEvent = null; 125 } 126 } 127 128 public String getLocalVariableEvent() { 129 return _localVariableEvent; 130 } 131 132 public void setLocalVariableNewValue(String localVariableNewValue) { 133 if ((localVariableNewValue != null) && (!localVariableNewValue.isEmpty())) { 134 this._localVariableNewValue = localVariableNewValue; 135 } else { 136 this._localVariableNewValue = null; 137 } 138 } 139 140 public String getLocalVariableNewValue() { 141 return _localVariableNewValue; 142 } 143 144 @Override 145 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 146 var tempNamedBeanReferences = new ArrayList<NamedBeanReference>(_namedBeanReferences.values()); 147 for (NamedBeanReference reference : tempNamedBeanReferences) { 148 if (reference._type.getClazz().isAssignableFrom(evt.getOldValue().getClass())) { 149 if ((reference._handle != null) && evt.getOldValue().equals(reference._handle.getBean())) { 150 if ("CanDelete".equals(evt.getPropertyName())) { // No I18N 151 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 152 throw new PropertyVetoException(getDisplayName(), e); 153 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 154 _namedBeanReferences.remove(reference._name, reference); 155 } 156 } 157 } 158 } 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public Category getCategory() { 164 return Category.OTHER; 165 } 166 167 /** {@inheritDoc} */ 168 @Override 169 public void execute() { 170 // The main purpose of this action is only to listen on property 171 // changes of the registered beans and execute the ConditionalNG 172 // when it happens. 173 174 synchronized(this) { 175 String namedBean; 176 String event; 177 String newValue; 178 179 PropertyChangeEvent evt = _eventQueue.poll(); 180 if (evt != null) { 181 namedBean = ((NamedBean)evt.getSource()).getDisplayName(); 182 event = evt.getPropertyName(); 183 newValue = evt.getNewValue() != null ? evt.getNewValue().toString() : null; 184 } else { 185 namedBean = null; 186 event = null; 187 newValue = null; 188 } 189 190 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 191 192 if (_localVariableNamedBean != null) { 193 symbolTable.setValue(_localVariableNamedBean, namedBean); 194 } 195 if (_localVariableEvent != null) { 196 symbolTable.setValue(_localVariableEvent, event); 197 } 198 if (_localVariableNewValue != null) { 199 symbolTable.setValue(_localVariableNewValue, newValue); 200 } 201 202 if (!_eventQueue.isEmpty()) { 203 getConditionalNG().execute(); 204 } 205 } 206 } 207 208 @Override 209 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 210 throw new UnsupportedOperationException("Not supported."); 211 } 212 213 @Override 214 public int getChildCount() { 215 return 0; 216 } 217 218 @Override 219 public String getShortDescription(Locale locale) { 220 return Bundle.getMessage(locale, "ActionListenOnBeans_Short"); 221 } 222 223 @Override 224 public String getLongDescription(Locale locale) { 225 return Bundle.getMessage(locale, "ActionListenOnBeans_Long"); 226 } 227 228 /** {@inheritDoc} */ 229 @Override 230 public void setup() { 231 // Do nothing 232 } 233 234 /** {@inheritDoc} */ 235 @Override 236 public void registerListenersForThisClass() { 237 if (_listenersAreRegistered) return; 238 239 for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) { 240 if (namedBeanReference._handle != null) { 241 if (!namedBeanReference._listenOnAllProperties 242 && (namedBeanReference._type.getPropertyName() != null)) { 243 namedBeanReference._handle.getBean() 244 .addPropertyChangeListener(namedBeanReference._type.getPropertyName(), this); 245 } else { 246 namedBeanReference._handle.getBean() 247 .addPropertyChangeListener(this); 248 } 249 } 250 } 251 _listenersAreRegistered = true; 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public void unregisterListenersForThisClass() { 257 if (!_listenersAreRegistered) return; 258 259 for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) { 260 if (namedBeanReference._handle != null) { 261 if (!namedBeanReference._listenOnAllProperties 262 && (namedBeanReference._type.getPropertyName() != null)) { 263 namedBeanReference._handle.getBean() 264 .removePropertyChangeListener(namedBeanReference._type.getPropertyName(), this); 265 } else { 266 namedBeanReference._handle.getBean() 267 .removePropertyChangeListener(this); 268 } 269 } 270 } 271 _listenersAreRegistered = false; 272 } 273 274 /** {@inheritDoc} */ 275 @Override 276 public void propertyChange(PropertyChangeEvent evt) { 277 boolean isQueueEmpty; 278 synchronized(this) { 279 isQueueEmpty = _eventQueue.isEmpty(); 280 _eventQueue.add(evt); 281 } 282 if (isQueueEmpty) { 283 getConditionalNG().execute(); 284 } 285 } 286 287 /** {@inheritDoc} */ 288 @Override 289 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 290 log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report); 291 for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) { 292 if (namedBeanReference._handle != null) { 293 if (bean.equals(namedBeanReference._handle.getBean())) { 294 report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription())); 295 } 296 } 297 } 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public void disposeMe() { 303 } 304 305 306 public static class NamedBeanReference { 307 308 private String _name; 309 private NamedBeanType _type; 310 private NamedBeanHandle<? extends NamedBean> _handle; 311 private boolean _listenOnAllProperties = false; 312 313 public NamedBeanReference(NamedBeanReference ref) { 314 this(ref._handle, ref._type, ref._listenOnAllProperties); 315 } 316 317 public NamedBeanReference(String name, NamedBeanType type, boolean all) { 318 _name = name; 319 _type = type; 320 _listenOnAllProperties = all; 321 322 if (_type != null) { 323 NamedBean bean = _type.getManager().getNamedBean(name); 324 if (bean != null) { 325 _handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(_name, bean); 326 } 327 } 328 } 329 330 public NamedBeanReference(NamedBeanHandle<? extends NamedBean> handle, NamedBeanType type, boolean all) { 331 _name = handle != null ? handle.getName() : null; 332 _type = type; 333 _listenOnAllProperties = all; 334 _handle = handle; 335 } 336 337 public String getName() { 338 return _name; 339 } 340 341 public void setName(String name) { 342 _name = name; 343 updateHandle(); 344 } 345 346 public void setName(NamedBean bean) { 347 if (bean != null) { 348 _handle = InstanceManager.getDefault(NamedBeanHandleManager.class) 349 .getNamedBeanHandle(bean.getDisplayName(), bean); 350 _name = _handle.getName(); 351 } else { 352 _name = null; 353 _handle = null; 354 } 355 } 356 357 public void setName(NamedBeanHandle<? extends NamedBean> handle) { 358 if (handle != null) { 359 _handle = handle; 360 _name = _handle.getName(); 361 } else { 362 _name = null; 363 _handle = null; 364 } 365 } 366 367 public NamedBeanType getType() { 368 return _type; 369 } 370 371 public void setType(NamedBeanType type) { 372 if (type == null) { 373 log.warn("type is null"); 374 type = NamedBeanType.Turnout; 375 } 376 _type = type; 377 _handle = null; 378 } 379 380 public NamedBeanHandle<? extends NamedBean> getHandle() { 381 return _handle; 382 } 383 384 private void updateHandle() { 385 if (_type != null && _name != null && !_name.isEmpty()) { 386 NamedBean bean = _type.getManager().getNamedBean(_name); 387 if (bean != null) { 388 _handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(_name, bean); 389 } else { 390 log.warn("Cannot find named bean {} in manager for {}", _name, _type.getManager().getBeanTypeHandled()); 391 _handle = null; 392 } 393 } else { 394 _handle = null; 395 } 396 } 397 398 public boolean getListenOnAllProperties() { 399 return _listenOnAllProperties; 400 } 401 402 public void setListenOnAllProperties(boolean listenOnAllProperties) { 403 _listenOnAllProperties = listenOnAllProperties; 404 } 405 406 // This method is used by ListenOnBeansTableModel 407 @Override 408 public String toString() { 409 if (_handle != null) return _handle.getName(); 410 else return ""; 411 } 412 } 413 414 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeans.class); 415 416}