001package jmri.jmrit.logixng.actions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006import java.util.concurrent.atomic.AtomicReference; 007 008import jmri.*; 009import jmri.jmrit.logixng.*; 010import jmri.jmrit.logixng.util.*; 011import jmri.jmrit.logixng.util.parser.*; 012import jmri.util.ThreadingUtil; 013 014/** 015 * Executes an action when the expression is True. 016 * 017 * @author Daniel Bergqvist Copyright 2018 018 */ 019public class ForEach extends AbstractDigitalAction 020 implements FemaleSocketListener, PropertyChangeListener { 021 022 private final LogixNG_SelectString _selectVariable = 023 new LogixNG_SelectString(this, this); 024 025 private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean = 026 new LogixNG_SelectNamedBean<>( 027 this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this); 028 029 private boolean _useCommonSource = true; 030 private CommonManager _commonManager = CommonManager.Sensors; 031 private UserSpecifiedSource _userSpecifiedSource = UserSpecifiedSource.Variable; 032 private String _formula = ""; 033 private ExpressionNode _expressionNode; 034 private String _variableName = ""; 035 private String _socketSystemName; 036 private final FemaleDigitalActionSocket _socket; 037 038 public ForEach(String sys, String user) { 039 super(sys, user); 040 _socket = InstanceManager.getDefault(DigitalActionManager.class) 041 .createFemaleSocket(this, this, "A"); 042 } 043 044 @Override 045 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 046 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 047 String sysName = systemNames.get(getSystemName()); 048 String userName = userNames.get(getSystemName()); 049 if (sysName == null) sysName = manager.getAutoSystemName(); 050 ForEach copy = new ForEach(sysName, userName); 051 copy.setComment(getComment()); 052 copy.setUseCommonSource(_useCommonSource); 053 copy.setCommonManager(_commonManager); 054 copy.setUserSpecifiedSource(_userSpecifiedSource); 055 _selectVariable.copy(copy._selectVariable); 056 _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean); 057 copy.setFormula(_formula); 058 copy.setLocalVariableName(_variableName); 059 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 060 } 061 062 public LogixNG_SelectString getSelectVariable() { 063 return _selectVariable; 064 } 065 066 public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() { 067 return _selectMemoryNamedBean; 068 } 069 070 public void setUseCommonSource(boolean commonSource) { 071 this._useCommonSource = commonSource; 072 } 073 074 public boolean isUseCommonSource() { 075 return _useCommonSource; 076 } 077 078 public void setCommonManager(CommonManager commonManager) throws ParserException { 079 _commonManager = commonManager; 080 parseFormula(); 081 } 082 083 public CommonManager getCommonManager() { 084 return _commonManager; 085 } 086 087 public void setUserSpecifiedSource(UserSpecifiedSource userSpecifiedSource) throws ParserException { 088 _userSpecifiedSource = userSpecifiedSource; 089 parseFormula(); 090 } 091 092 public UserSpecifiedSource getUserSpecifiedSource() { 093 return _userSpecifiedSource; 094 } 095 096 public void setFormula(String formula) throws ParserException { 097 _formula = formula; 098 parseFormula(); 099 } 100 101 public String getFormula() { 102 return _formula; 103 } 104 105 private void parseFormula() throws ParserException { 106 if (_userSpecifiedSource == UserSpecifiedSource.Formula) { 107 Map<String, Variable> variables = new HashMap<>(); 108 109 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 110 _expressionNode = parser.parseExpression(_formula); 111 } else { 112 _expressionNode = null; 113 } 114 } 115 116 /** 117 * Get name of local variable 118 * @return name of local variable 119 */ 120 public String getLocalVariableName() { 121 return _variableName; 122 } 123 124 /** 125 * Set name of local variable 126 * @param localVariableName name of local variable 127 */ 128 public void setLocalVariableName(String localVariableName) { 129 _variableName = localVariableName; 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 public Category getCategory() { 135 return Category.FLOW_CONTROL; 136 } 137 138 /** {@inheritDoc} */ 139 @Override 140 @SuppressWarnings("unchecked") 141 public void execute() throws JmriException { 142 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 143 144 AtomicReference<Collection<? extends Object>> collectionRef = new AtomicReference<>(); 145 AtomicReference<JmriException> ref = new AtomicReference<>(); 146 147 final ConditionalNG conditionalNG = getConditionalNG(); 148 149 if (_useCommonSource) { 150 collectionRef.set(_commonManager.getManager().getNamedBeanSet()); 151 } else { 152 ThreadingUtil.runOnLayoutWithJmriException(() -> { 153 154 Object value = null; 155 156 switch (_userSpecifiedSource) { 157 case Variable: 158 String otherLocalVariable = _selectVariable.evaluateValue(getConditionalNG()); 159 Object variableValue = conditionalNG 160 .getSymbolTable().getValue(otherLocalVariable); 161 162 value = variableValue; 163 break; 164 165 case Memory: 166 Memory memory = _selectMemoryNamedBean.evaluateNamedBean(getConditionalNG()); 167 if (memory != null) { 168 value = memory.getValue(); 169 } else { 170 log.warn("ForEach memory is null"); 171 } 172 break; 173 174 case Formula: 175 if (!_formula.isEmpty() && _expressionNode != null) { 176 value = _expressionNode.calculate(conditionalNG.getSymbolTable()); 177 } 178 break; 179 180 default: 181 // Throw exception 182 throw new IllegalArgumentException("_userSpecifiedSource has invalid value: {}" + _userSpecifiedSource.name()); 183 } 184 185 if (value instanceof Manager) { 186 collectionRef.set(((Manager<? extends NamedBean>) value).getNamedBeanSet()); 187 } else if (value != null && value.getClass().isArray()) { 188 // Note: (Object[]) is needed to tell that the parameter is an array and not a vararg argument 189 // See: https://stackoverflow.com/questions/2607289/converting-array-to-list-in-java/2607327#2607327 190 collectionRef.set(Arrays.asList((Object[])value)); 191 } else if (value instanceof Collection) { 192 collectionRef.set((Collection<? extends Object>) value); 193 } else if (value instanceof Map) { 194 collectionRef.set(((Map<?,?>) value).entrySet()); 195 } else { 196 throw new JmriException(Bundle.getMessage("ForEach_InvalidValue", 197 value != null ? value.getClass().getName() : null)); 198 } 199 }); 200 } 201 202 if (ref.get() != null) throw ref.get(); 203 204 for (Object o : collectionRef.get()) { 205 symbolTable.setValue(_variableName, o); 206 try { 207 _socket.execute(); 208 } catch (BreakException e) { 209 break; 210 } catch (ContinueException e) { 211 // Do nothing, just catch it. 212 } 213 } 214 } 215 216 @Override 217 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 218 switch (index) { 219 case 0: 220 return _socket; 221 222 default: 223 throw new IllegalArgumentException( 224 String.format("index has invalid value: %d", index)); 225 } 226 } 227 228 @Override 229 public int getChildCount() { 230 return 1; 231 } 232 233 @Override 234 public void connected(FemaleSocket socket) { 235 if (socket == _socket) { 236 _socketSystemName = socket.getConnectedSocket().getSystemName(); 237 } else { 238 throw new IllegalArgumentException("unkown socket"); 239 } 240 } 241 242 @Override 243 public void disconnected(FemaleSocket socket) { 244 if (socket == _socket) { 245 _socketSystemName = null; 246 } else { 247 throw new IllegalArgumentException("unkown socket"); 248 } 249 } 250 251 @Override 252 public String getShortDescription(Locale locale) { 253 return Bundle.getMessage(locale, "ForEach_Short"); 254 } 255 256 @Override 257 public String getLongDescription(Locale locale) { 258 if (_useCommonSource) { 259 return Bundle.getMessage(locale, "ForEach_Long_Common", 260 _commonManager.toString(), _variableName, _socket.getName()); 261 } else { 262 switch (_userSpecifiedSource) { 263 case Variable: 264 return Bundle.getMessage(locale, "ForEach_Long_LocalVariable", 265 _selectVariable.getDescription(locale), _variableName, _socket.getName()); 266 267 case Memory: 268 return Bundle.getMessage(locale, "ForEach_Long_Memory", 269 _selectMemoryNamedBean.getDescription(locale), _variableName, _socket.getName()); 270 271 case Formula: 272 return Bundle.getMessage(locale, "ForEach_Long_Formula", 273 _formula, _variableName, _socket.getName()); 274 275 default: 276 throw new IllegalArgumentException("_variableOperation has invalid value: " + _userSpecifiedSource.name()); 277 } 278 } 279 } 280 281 public FemaleDigitalActionSocket getSocket() { 282 return _socket; 283 } 284 285 public String getSocketSystemName() { 286 return _socketSystemName; 287 } 288 289 public void setSocketSystemName(String systemName) { 290 _socketSystemName = systemName; 291 } 292 293 /** {@inheritDoc} */ 294 @Override 295 public void setup() { 296 try { 297 if ( !_socket.isConnected() 298 || !_socket.getConnectedSocket().getSystemName() 299 .equals(_socketSystemName)) { 300 301 String socketSystemName = _socketSystemName; 302 _socket.disconnect(); 303 if (socketSystemName != null) { 304 MaleSocket maleSocket = 305 InstanceManager.getDefault(DigitalActionManager.class) 306 .getBySystemName(socketSystemName); 307 _socket.disconnect(); 308 if (maleSocket != null) { 309 _socket.connect(maleSocket); 310 maleSocket.setup(); 311 } else { 312 log.error("cannot load digital action {}", socketSystemName); 313 } 314 } 315 } else { 316 _socket.getConnectedSocket().setup(); 317 } 318 } catch (SocketAlreadyConnectedException ex) { 319 // This shouldn't happen and is a runtime error if it does. 320 throw new RuntimeException("socket is already connected"); 321 } 322 } 323 324 /** {@inheritDoc} */ 325 @Override 326 public void registerListenersForThisClass() { 327 if (!_listenersAreRegistered) { 328 if (_userSpecifiedSource == UserSpecifiedSource.Memory) { 329 _selectMemoryNamedBean.registerListeners(); 330 } 331 _listenersAreRegistered = true; 332 } 333 } 334 335 /** {@inheritDoc} */ 336 @Override 337 public void unregisterListenersForThisClass() { 338 if (_listenersAreRegistered) { 339 if (_userSpecifiedSource == UserSpecifiedSource.Memory) { 340 _selectMemoryNamedBean.unregisterListeners(); 341 } 342 _listenersAreRegistered = false; 343 } 344 } 345 346 /** {@inheritDoc} */ 347 @Override 348 public void disposeMe() { 349 } 350 351 /** {@inheritDoc} */ 352 @Override 353 public void propertyChange(PropertyChangeEvent evt) { 354 getConditionalNG().execute(); 355 } 356 357 358 public enum UserSpecifiedSource { 359 Variable(Bundle.getMessage("ForEach_UserSpecifiedSource_Variable")), 360 Memory(Bundle.getMessage("ForEach_UserSpecifiedSource_Memory")), 361 Formula(Bundle.getMessage("ForEach_UserSpecifiedSource_Formula")); 362 363 private final String _text; 364 365 private UserSpecifiedSource(String text) { 366 this._text = text; 367 } 368 369 @Override 370 public String toString() { 371 return _text; 372 } 373 374 } 375 376 377 378 379 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ForEach.class); 380 381}