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