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