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 AnalogFormula extends AbstractAnalogExpression 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 AnalogFormula(@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 AnalogFormula(@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 AnalogExpressionManager manager = InstanceManager.getDefault(AnalogExpressionManager.class); 063 String sysName = systemNames.get(getSystemName()); 064 String userName = userNames.get(getSystemName()); 065 if (sysName == null) sysName = manager.getAutoSystemName(); 066 AnalogFormula copy = new AnalogFormula(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 double evaluate() throws JmriException { 138 139 if (_formula.isEmpty()) { 140 return 0.0; 141 } 142 143 return TypeConversionUtil.convertToDouble(_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, "AnalogFormula_Short"); 186 } 187 188 @Override 189 public String getLongDescription(Locale locale) { 190 if (_formula.isEmpty()) { 191 return Bundle.getMessage(locale, "AnalogFormula_Long_Empty"); 192 } else { 193 return Bundle.getMessage(locale, "AnalogFormula_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 entry._manager = 315 socket.getConnectedSocket().getManager().getClass().getName(); 316 } 317 } 318 319 checkFreeSocket(); 320 } 321 322 @Override 323 public void disconnected(FemaleSocket socket) { 324 for (ExpressionEntry entry : _expressionEntries) { 325 if (socket == entry._socket) { 326 entry._socketSystemName = null; 327 entry._manager = 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 analog 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(AnalogFormula.class); 432}