001package jmri.jmrit.logixng.actions; 002 003import java.util.List; 004import java.util.ArrayList; 005import java.util.HashMap; 006import java.util.Locale; 007import java.util.Map; 008 009import javax.annotation.Nonnull; 010import javax.annotation.CheckForNull; 011 012import jmri.InstanceManager; 013import jmri.JmriException; 014import jmri.Manager; 015import jmri.jmrit.logixng.*; 016import jmri.jmrit.logixng.implementation.DefaultFemaleGenericExpressionSocket; 017import jmri.jmrit.logixng.util.parser.ParserException; 018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 019import jmri.jmrit.logixng.util.parser.Variable; 020import jmri.jmrit.logixng.util.parser.GenericExpressionVariable; 021import jmri.jmrit.logixng.util.parser.ExpressionNode; 022 023/** 024 * This action evaluates the formula. 025 * 026 * @author Daniel Bergqvist Copyright 2021 027 */ 028public class DigitalFormula extends AbstractDigitalAction implements FemaleSocketListener { 029 030 private String _formula = ""; 031 private ExpressionNode _expressionNode; 032 private final List<ExpressionEntry> _expressionEntries = new ArrayList<>(); 033 private boolean _disableCheckForUnconnectedSocket = false; 034 035 /** 036 * Create a new instance of Formula with system name and user name. 037 * @param sys the system name 038 * @param user the user name 039 */ 040 public DigitalFormula(@Nonnull String sys, @CheckForNull String user) { 041 super(sys, user); 042 _expressionEntries 043 .add(new ExpressionEntry(createFemaleSocket(this, this, getNewSocketName()))); 044 } 045 046 /** 047 * Create a new instance of Formula with system name and user name. 048 * @param sys the system name 049 * @param user the user name 050 * @param expressionSystemNames a list of system names for the expressions 051 * this formula uses 052 */ 053 public DigitalFormula(@Nonnull String sys, @CheckForNull String user, 054 List<SocketData> expressionSystemNames) { 055 super(sys, user); 056 setExpressionSystemNames(expressionSystemNames); 057 } 058 059 @Override 060 protected String getPreferredSocketPrefix() { 061 return "E"; 062 } 063 064 @Override 065 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 066 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 067 String sysName = systemNames.get(getSystemName()); 068 String userName = userNames.get(getSystemName()); 069 if (sysName == null) sysName = manager.getAutoSystemName(); 070 DigitalFormula copy = new DigitalFormula(sysName, userName); 071 copy.setComment(getComment()); 072 copy.setNumSockets(getChildCount()); 073 copy.setFormula(_formula); 074 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 075 } 076 077 private void setExpressionSystemNames(List<SocketData> systemNames) { 078 if (!_expressionEntries.isEmpty()) { 079 throw new RuntimeException("expression system names cannot be set more than once"); 080 } 081 082 for (SocketData socketData : systemNames) { 083 FemaleGenericExpressionSocket socket = 084 createFemaleSocket(this, this, socketData._socketName); 085// FemaleGenericExpressionSocket socket = 086// InstanceManager.getDefault(AnalogExpressionManager.class) 087// .createFemaleSocket(this, this, entry.getKey()); 088 089 _expressionEntries.add(new ExpressionEntry(socket, socketData._socketSystemName, socketData._manager)); 090 } 091 } 092 093 public String getExpressionSystemName(int index) { 094 return _expressionEntries.get(index)._socketSystemName; 095 } 096 097 public String getExpressionManager(int index) { 098 return _expressionEntries.get(index)._manager; 099 } 100 101 private FemaleGenericExpressionSocket createFemaleSocket( 102 Base parent, FemaleSocketListener listener, String socketName) { 103 104 return new DefaultFemaleGenericExpressionSocket( 105 FemaleGenericExpressionSocket.SocketType.GENERIC, parent, listener, socketName); 106 } 107 108 public final void setFormula(String formula) throws ParserException { 109 Map<String, Variable> variables = new HashMap<>(); 110 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 111 for (int i=0; i < getChildCount(); i++) { 112 Variable v = new GenericExpressionVariable((FemaleGenericExpressionSocket)getChild(i)); 113 variables.put(v.getName(), v); 114 } 115 _expressionNode = parser.parseExpression(formula); 116 // parseExpression() may throw an exception and we don't want to set 117 // the field _formula until we now parseExpression() has succeeded. 118 _formula = formula; 119 } 120 121 public String getFormula() { 122 return _formula; 123 } 124 125 private void parseFormula() { 126 try { 127 setFormula(_formula); 128 } catch (ParserException e) { 129 log.error("Unexpected exception when parsing the formula", e); 130 } 131 } 132 133 /** {@inheritDoc} */ 134 @Override 135 public Category getCategory() { 136 return Category.COMMON; 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public void execute() throws JmriException { 142 143 if (_formula.isEmpty()) return; 144 145 _expressionNode.calculate(getConditionalNG().getSymbolTable()); 146 } 147 148 @Override 149 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 150 return _expressionEntries.get(index)._socket; 151 } 152 153 @Override 154 public int getChildCount() { 155 return _expressionEntries.size(); 156 } 157 158 public void setChildCount(int count) { 159 List<FemaleSocket> addList = new ArrayList<>(); 160 List<FemaleSocket> removeList = new ArrayList<>(); 161 162 // Is there too many children? 163 while (_expressionEntries.size() > count) { 164 int childNo = _expressionEntries.size()-1; 165 FemaleSocket socket = _expressionEntries.get(childNo)._socket; 166 if (socket.isConnected()) { 167 socket.disconnect(); 168 } 169 removeList.add(_expressionEntries.get(childNo)._socket); 170 _expressionEntries.remove(childNo); 171 } 172 173 // Is there not enough children? 174 while (_expressionEntries.size() < count) { 175 FemaleGenericExpressionSocket socket = 176 createFemaleSocket(this, this, getNewSocketName()); 177 _expressionEntries.add(new ExpressionEntry(socket)); 178 addList.add(socket); 179 } 180 parseFormula(); 181 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, addList); 182 } 183 184 @Override 185 public String getShortDescription(Locale locale) { 186 return Bundle.getMessage(locale, "DigitalFormula_Short"); 187 } 188 189 @Override 190 public String getLongDescription(Locale locale) { 191 if (_formula.isEmpty()) { 192 return Bundle.getMessage(locale, "DigitalFormula_Long_Empty"); 193 } else { 194 return Bundle.getMessage(locale, "DigitalFormula_Long", _formula); 195 } 196 } 197 198 // This method ensures that we have enough of children 199 private void setNumSockets(int num) { 200 List<FemaleSocket> addList = new ArrayList<>(); 201 202 // Is there not enough children? 203 while (_expressionEntries.size() < num) { 204 FemaleGenericExpressionSocket socket = 205 createFemaleSocket(this, this, getNewSocketName()); 206 _expressionEntries.add(new ExpressionEntry(socket)); 207 addList.add(socket); 208 } 209 parseFormula(); 210 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 211 } 212 213 private void checkFreeSocket() { 214 boolean hasFreeSocket = false; 215 216 for (ExpressionEntry entry : _expressionEntries) { 217 hasFreeSocket |= !entry._socket.isConnected(); 218 } 219 if (!hasFreeSocket) { 220 FemaleGenericExpressionSocket socket = 221 createFemaleSocket(this, this, getNewSocketName()); 222 _expressionEntries.add(new ExpressionEntry(socket)); 223 224 List<FemaleSocket> list = new ArrayList<>(); 225 list.add(socket); 226 parseFormula(); 227 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list); 228 } 229 } 230 231 /** {@inheritDoc} */ 232 @Override 233 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 234 switch (oper) { 235 case Remove: // Possible if socket is not connected 236 return ! getChild(index).isConnected(); 237 case InsertBefore: 238 return true; // Always possible 239 case InsertAfter: 240 return true; // Always possible 241 case MoveUp: 242 return index > 0; // Possible if not first socket 243 case MoveDown: 244 return index+1 < getChildCount(); // Possible if not last socket 245 default: 246 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 247 } 248 } 249 250 private void insertNewSocket(int index) { 251 FemaleGenericExpressionSocket socket = 252 createFemaleSocket(this, this, getNewSocketName()); 253 _expressionEntries.add(index, new ExpressionEntry(socket)); 254 255 List<FemaleSocket> addList = new ArrayList<>(); 256 addList.add(socket); 257 parseFormula(); 258 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 259 } 260 261 private void removeSocket(int index) { 262 List<FemaleSocket> removeList = new ArrayList<>(); 263 removeList.add(_expressionEntries.remove(index)._socket); 264 parseFormula(); 265 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 266 } 267 268 private void moveSocketDown(int index) { 269 ExpressionEntry temp = _expressionEntries.get(index); 270 _expressionEntries.set(index, _expressionEntries.get(index+1)); 271 _expressionEntries.set(index+1, temp); 272 273 List<FemaleSocket> list = new ArrayList<>(); 274 list.add(_expressionEntries.get(index)._socket); 275 list.add(_expressionEntries.get(index)._socket); 276 parseFormula(); 277 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public void doSocketOperation(int index, FemaleSocketOperation oper) { 283 switch (oper) { 284 case Remove: 285 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 286 removeSocket(index); 287 break; 288 case InsertBefore: 289 insertNewSocket(index); 290 break; 291 case InsertAfter: 292 insertNewSocket(index+1); 293 break; 294 case MoveUp: 295 if (index == 0) throw new UnsupportedOperationException("cannot move up first child"); 296 moveSocketDown(index-1); 297 break; 298 case MoveDown: 299 if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child"); 300 moveSocketDown(index); 301 break; 302 default: 303 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 304 } 305 } 306 307 @Override 308 public void connected(FemaleSocket socket) { 309 if (_disableCheckForUnconnectedSocket) return; 310 311 for (ExpressionEntry entry : _expressionEntries) { 312 if (socket == entry._socket) { 313 entry._socketSystemName = 314 socket.getConnectedSocket().getSystemName(); 315 } 316 } 317 318 checkFreeSocket(); 319 } 320 321 @Override 322 public void disconnected(FemaleSocket socket) { 323 for (ExpressionEntry entry : _expressionEntries) { 324 if (socket == entry._socket) { 325 entry._socketSystemName = null; 326 break; 327 } 328 } 329 } 330 331 /** {@inheritDoc} */ 332 @Override 333 public void socketNameChanged(FemaleSocket socket) { 334 parseFormula(); 335 } 336 337 /** {@inheritDoc} */ 338 @Override 339 public void setup() { 340 // We don't want to check for unconnected sockets while setup sockets 341 _disableCheckForUnconnectedSocket = true; 342 343 for (ExpressionEntry ee : _expressionEntries) { 344 try { 345 if ( !ee._socket.isConnected() 346 || !ee._socket.getConnectedSocket().getSystemName() 347 .equals(ee._socketSystemName)) { 348 349 String socketSystemName = ee._socketSystemName; 350 String manager = ee._manager; 351 ee._socket.disconnect(); 352 if (socketSystemName != null) { 353 Manager<? extends MaleSocket> m = 354 InstanceManager.getDefault(LogixNG_Manager.class) 355 .getManager(manager); 356 MaleSocket maleSocket = m.getBySystemName(socketSystemName); 357 if (maleSocket != null) { 358 ee._socket.connect(maleSocket); 359 maleSocket.setup(); 360 } else { 361 log.error("cannot load digital expression {}", socketSystemName); 362 } 363 } 364 } else { 365 ee._socket.getConnectedSocket().setup(); 366 } 367 } catch (SocketAlreadyConnectedException ex) { 368 // This shouldn't happen and is a runtime error if it does. 369 throw new RuntimeException("socket is already connected"); 370 } 371 } 372 373 parseFormula(); 374 checkFreeSocket(); 375 376 _disableCheckForUnconnectedSocket = false; 377 } 378 379 /** {@inheritDoc} */ 380 @Override 381 public void registerListenersForThisClass() { 382 // Do nothing 383 } 384 385 /** {@inheritDoc} */ 386 @Override 387 public void unregisterListenersForThisClass() { 388 // Do nothing 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public void disposeMe() { 394 } 395 396 397 public static class SocketData { 398 public final String _socketName; 399 public final String _socketSystemName; 400 public final String _manager; 401 402 public SocketData(String socketName, String socketSystemName, String manager) { 403 _socketName = socketName; 404 _socketSystemName = socketSystemName; 405 _manager = manager; 406 } 407 } 408 409 410 /* This class is public since ExpressionFormulaXml needs to access it. */ 411 public static class ExpressionEntry { 412 private final FemaleGenericExpressionSocket _socket; 413 private String _socketSystemName; 414 public String _manager; 415 416 public ExpressionEntry(FemaleGenericExpressionSocket socket, String socketSystemName, String manager) { 417 _socket = socket; 418 _socketSystemName = socketSystemName; 419 _manager = manager; 420 } 421 422 private ExpressionEntry(FemaleGenericExpressionSocket socket) { 423 this._socket = socket; 424 } 425 426 } 427 428 429 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalFormula.class); 430}