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