001package jmri.jmrit.logixng.actions; 002 003import jmri.jmrit.logixng.NamedBeanType; 004 005import java.beans.*; 006import java.util.*; 007 008import javax.annotation.Nonnull; 009 010import jmri.*; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.implementation.DefaultSymbolTable; 013 014import net.jcip.annotations.GuardedBy; 015 016/** 017 * This action listens on some beans and runs the ConditionalNG on property change. 018 * 019 * @author Daniel Bergqvist Copyright 2022 020 */ 021public class ActionListenOnBeansLocalVariable extends AbstractDigitalAction 022 implements FemaleSocketListener, PropertyChangeListener, VetoableChangeListener { 023 024 private NamedBeanType _namedBeanType = NamedBeanType.Light; 025 private boolean _listenOnAllProperties = false; 026 private String _localVariableBeanToListenOn; 027 private String _localVariableNamedBean; 028 private String _localVariableEvent; 029 private String _localVariableNewValue; 030 private final Map<NamedBean, String> _namedBeansEntries = new HashMap<>(); 031 private final InternalFemaleSocket _internalSocket = new InternalFemaleSocket(); 032 private String _executeSocketSystemName; 033 private final FemaleDigitalActionSocket _executeSocket; 034 035 @GuardedBy("this") 036 private final Deque<PropertyChangeEvent> _eventQueue = new ArrayDeque<>(); 037 038 039 public ActionListenOnBeansLocalVariable(String sys, String user) 040 throws BadUserNameException, BadSystemNameException { 041 super(sys, user); 042 _executeSocket = InstanceManager.getDefault(DigitalActionManager.class) 043 .createFemaleSocket(this, this, Bundle.getMessage("ShowDialog_SocketExecute")); 044 } 045 046 @Override 047 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) 048 throws JmriException { 049 050 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 051 String sysName = systemNames.get(getSystemName()); 052 String userName = userNames.get(getSystemName()); 053 if (sysName == null) sysName = manager.getAutoSystemName(); 054 ActionListenOnBeansLocalVariable copy = new ActionListenOnBeansLocalVariable(sysName, userName); 055 copy.setComment(getComment()); 056 copy.setNamedBeanType(_namedBeanType); 057 058 copy.setLocalVariableBeanToListenOn(_localVariableBeanToListenOn); 059 copy.setLocalVariableNamedBean(_localVariableNamedBean); 060 copy.setLocalVariableEvent(_localVariableEvent); 061 copy.setLocalVariableNewValue(_localVariableNewValue); 062 063 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 064 } 065 066 /** 067 * Get the type of the named beans 068 * @return the type of named beans 069 */ 070 public NamedBeanType getNamedBeanType() { 071 return _namedBeanType; 072 } 073 074 /** 075 * Set the type of the named beans 076 * @param namedBeanType the type of the named beans 077 */ 078 public void setNamedBeanType(@Nonnull NamedBeanType namedBeanType) { 079 if (namedBeanType == null) { 080 throw new IllegalArgumentException("namedBeanType must not be null"); 081 } 082 _namedBeanType = namedBeanType; 083 } 084 085 public boolean getListenOnAllProperties() { 086 return _listenOnAllProperties; 087 } 088 089 public void setListenOnAllProperties(boolean listenOnAllProperties) { 090 _listenOnAllProperties = listenOnAllProperties; 091 } 092 093 public void setLocalVariableBeanToListenOn(String localVariableBeanToListenOn) { 094 if ((localVariableBeanToListenOn != null) && (!localVariableBeanToListenOn.isEmpty())) { 095 this._localVariableBeanToListenOn = localVariableBeanToListenOn; 096 } else { 097 this._localVariableBeanToListenOn = null; 098 } 099 } 100 101 public String getLocalVariableBeanToListenOn() { 102 return _localVariableBeanToListenOn; 103 } 104 105 public void setLocalVariableNamedBean(String localVariableNamedBean) { 106 if ((localVariableNamedBean != null) && (!localVariableNamedBean.isEmpty())) { 107 this._localVariableNamedBean = localVariableNamedBean; 108 } else { 109 this._localVariableNamedBean = null; 110 } 111 } 112 113 public String getLocalVariableNamedBean() { 114 return _localVariableNamedBean; 115 } 116 117 public void setLocalVariableEvent(String localVariableEvent) { 118 if ((localVariableEvent != null) && (!localVariableEvent.isEmpty())) { 119 this._localVariableEvent = localVariableEvent; 120 } else { 121 this._localVariableEvent = null; 122 } 123 } 124 125 public String getLocalVariableEvent() { 126 return _localVariableEvent; 127 } 128 129 public void setLocalVariableNewValue(String localVariableNewValue) { 130 if ((localVariableNewValue != null) && (!localVariableNewValue.isEmpty())) { 131 this._localVariableNewValue = localVariableNewValue; 132 } else { 133 this._localVariableNewValue = null; 134 } 135 } 136 137 public String getLocalVariableNewValue() { 138 return _localVariableNewValue; 139 } 140 141 public FemaleDigitalActionSocket getExecuteSocket() { 142 return _executeSocket; 143 } 144 145 public String getExecuteSocketSystemName() { 146 return _executeSocketSystemName; 147 } 148 149 public void setExecuteSocketSystemName(String systemName) { 150 _executeSocketSystemName = systemName; 151 } 152 153 /** {@inheritDoc} */ 154 @Override 155 public Category getCategory() { 156 return Category.OTHER; 157 } 158 159 /** {@inheritDoc} */ 160 @Override 161 public void execute() { 162 if (_localVariableBeanToListenOn != null 163 && !_localVariableBeanToListenOn.isBlank()) { 164 165 ConditionalNG conditionalNG = getConditionalNG(); 166 _internalSocket._conditionalNG = conditionalNG; 167 _internalSocket._newSymbolTable = new DefaultSymbolTable(conditionalNG.getSymbolTable()); 168 169 SymbolTable symbolTable = conditionalNG.getSymbolTable(); 170 Object value = symbolTable.getValue(_localVariableBeanToListenOn); 171 172 NamedBean namedBean = null; 173 174 if (value instanceof NamedBean) { 175 namedBean = (NamedBean) value; 176 } else if (value != null) { 177 namedBean = _namedBeanType.getManager().getNamedBean(value.toString()); 178 } 179 180 if (namedBean != null) { 181 if (!_namedBeansEntries.containsKey(namedBean)) { 182 _namedBeansEntries.put(namedBean, _namedBeanType.getPropertyName()); 183 addPropertyListener(namedBean, _namedBeanType.getPropertyName()); 184 } 185 } else { 186 log.warn("The named bean \"{}\" cannot be found in the manager for {}", value, _namedBeanType.toString()); 187 } 188 } 189 } 190 191 @Override 192 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 193 switch (index) { 194 case 0: 195 return _executeSocket; 196 197 default: 198 throw new IllegalArgumentException( 199 String.format("index has invalid value: %d", index)); 200 } 201 } 202 203 @Override 204 public int getChildCount() { 205 return 1; 206 } 207 208 @Override 209 public void connected(FemaleSocket socket) { 210 if (socket == _executeSocket) { 211 _executeSocketSystemName = socket.getConnectedSocket().getSystemName(); 212 } else { 213 throw new IllegalArgumentException("unkown socket"); 214 } 215 } 216 217 @Override 218 public void disconnected(FemaleSocket socket) { 219 if (socket == _executeSocket) { 220 _executeSocketSystemName = null; 221 } else { 222 throw new IllegalArgumentException("unkown socket"); 223 } 224 } 225 226 @Override 227 public String getShortDescription(Locale locale) { 228 return Bundle.getMessage(locale, "ActionListenOnBeansLocalVariable_Short"); 229 } 230 231 @Override 232 public String getLongDescription(Locale locale) { 233 return Bundle.getMessage(locale, 234 "ActionListenOnBeansLocalVariable_Long", 235 _localVariableBeanToListenOn, 236 _namedBeanType.toString()); 237 } 238 239 /** {@inheritDoc} */ 240 @Override 241 public void setup() { 242 try { 243 if (!_executeSocket.isConnected() 244 || !_executeSocket.getConnectedSocket().getSystemName() 245 .equals(_executeSocketSystemName)) { 246 247 String socketSystemName = _executeSocketSystemName; 248 249 _executeSocket.disconnect(); 250 251 if (socketSystemName != null) { 252 MaleSocket maleSocket = 253 InstanceManager.getDefault(DigitalActionManager.class) 254 .getBySystemName(socketSystemName); 255 if (maleSocket != null) { 256 _executeSocket.connect(maleSocket); 257 maleSocket.setup(); 258 } else { 259 log.error("cannot load digital action {}", socketSystemName); 260 } 261 } 262 } else { 263 _executeSocket.getConnectedSocket().setup(); 264 } 265 } catch (SocketAlreadyConnectedException ex) { 266 // This shouldn't happen and is a runtime error if it does. 267 throw new RuntimeException("socket is already connected"); 268 } 269 } 270 271 private void addPropertyListener(NamedBean namedBean, String property) { 272 if (!_listenOnAllProperties 273 && (property != null)) { 274 namedBean.addPropertyChangeListener(property, this); 275 } else { 276 namedBean.addPropertyChangeListener(this); 277 } 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public void registerListenersForThisClass() { 283 if (_listenersAreRegistered) return; 284 285 for (Map.Entry<NamedBean, String> namedBeanEntry : _namedBeansEntries.entrySet()) { 286 addPropertyListener(namedBeanEntry.getKey(), namedBeanEntry.getValue()); 287 } 288 _listenersAreRegistered = true; 289 } 290 291 /** {@inheritDoc} */ 292 @Override 293 public void unregisterListenersForThisClass() { 294 if (!_listenersAreRegistered) return; 295 296 for (Map.Entry<NamedBean, String> namedBeanEntry : _namedBeansEntries.entrySet()) { 297 if (!_listenOnAllProperties 298 && (namedBeanEntry.getValue() != null)) { 299 namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this); 300 } else { 301 namedBeanEntry.getKey().removePropertyChangeListener(this); 302 } 303 namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this); 304 } 305 _listenersAreRegistered = false; 306 } 307 308 /** {@inheritDoc} */ 309 @Override 310 public void propertyChange(PropertyChangeEvent evt) { 311 boolean isQueueEmpty; 312 synchronized(this) { 313 isQueueEmpty = _eventQueue.isEmpty(); 314 _eventQueue.add(evt); 315 } 316 if (isQueueEmpty) { 317 getConditionalNG().execute(_internalSocket); 318 } 319 } 320 321 /** {@inheritDoc} */ 322 @Override 323 public void disposeMe() { 324 } 325 326 327 /** {@inheritDoc} */ 328 @Override 329 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 330 } 331 332 333 private class InternalFemaleSocket extends jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket { 334 335 private ConditionalNG _conditionalNG; 336 private SymbolTable _newSymbolTable; 337 338 public InternalFemaleSocket() { 339 super(null, new FemaleSocketListener(){ 340 @Override 341 public void connected(FemaleSocket socket) { 342 // Do nothing 343 } 344 345 @Override 346 public void disconnected(FemaleSocket socket) { 347 // Do nothing 348 } 349 }, "A"); 350 } 351 352 @Override 353 public void execute() throws JmriException { 354 if (_executeSocket != null) { 355 356 synchronized(this) { 357 SymbolTable oldSymbolTable = _conditionalNG.getSymbolTable(); 358 _conditionalNG.setSymbolTable(_newSymbolTable); 359 360 String namedBean; 361 String event; 362 String newValue; 363 364 PropertyChangeEvent evt = _eventQueue.poll(); 365 if (evt != null) { 366 namedBean = ((NamedBean)evt.getSource()).getDisplayName(); 367 event = evt.getPropertyName(); 368 newValue = evt.getNewValue() != null ? evt.getNewValue().toString() : null; 369 } else { 370 namedBean = null; 371 event = null; 372 newValue = null; 373 } 374 375 if (_localVariableNamedBean != null) { 376 _newSymbolTable.setValue(_localVariableNamedBean, namedBean); 377 } 378 if (_localVariableEvent != null) { 379 _newSymbolTable.setValue(_localVariableEvent, event); 380 } 381 if (_localVariableNewValue != null) { 382 _newSymbolTable.setValue(_localVariableNewValue, newValue); 383 } 384 385 _executeSocket.execute(); 386 _conditionalNG.setSymbolTable(oldSymbolTable); 387 388 if (!_eventQueue.isEmpty()) { 389 _conditionalNG.execute(_internalSocket); 390 } 391 } 392 } 393 } 394 395 } 396 397 398 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeansLocalVariable.class); 399 400}