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