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