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