001package jmri.jmrit.logixng.expressions; 002 003import java.util.List; 004import java.util.ArrayList; 005import java.util.Locale; 006import java.util.Map; 007 008import jmri.InstanceManager; 009import jmri.JmriException; 010import jmri.jmrit.logixng.*; 011 012/** 013 * Evaluates to True if all of the children expressions evaluate to true. 014 * 015 * @author Daniel Bergqvist Copyright 2018 016 */ 017public class And extends AbstractDigitalExpression implements FemaleSocketListener { 018 019 private Type _type = Type.EvaluateAll; 020 private final List<ExpressionEntry> _expressionEntries = new ArrayList<>(); 021 private boolean disableCheckForUnconnectedSocket = false; 022 023 public And(String sys, String user) 024 throws BadUserNameException, BadSystemNameException { 025 super(sys, user); 026 _expressionEntries 027 .add(new ExpressionEntry(InstanceManager.getDefault(DigitalExpressionManager.class) 028 .createFemaleSocket(this, this, getNewSocketName()))); 029 } 030 031 public And(String sys, String user, List<Map.Entry<String, String>> expressionSystemNames) 032 throws BadUserNameException, BadSystemNameException { 033 super(sys, user); 034 setExpressionSystemNames(expressionSystemNames); 035 } 036 037 @Override 038 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 039 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 040 String sysName = systemNames.get(getSystemName()); 041 String userName = userNames.get(getSystemName()); 042 if (sysName == null) sysName = manager.getAutoSystemName(); 043 And copy = new And(sysName, userName); 044 copy.setComment(getComment()); 045 copy.setType(_type); 046 copy.setNumSockets(getChildCount()); 047 return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames); 048 } 049 050 /** 051 * Get the type. 052 * @return the type 053 */ 054 public Type getType() { 055 return _type; 056 } 057 058 /** 059 * Set the type. 060 * @param type the type 061 */ 062 public void setType(Type type) { 063 _type = type; 064 } 065 066 private void setExpressionSystemNames(List<Map.Entry<String, String>> systemNames) { 067 if (!_expressionEntries.isEmpty()) { 068 throw new RuntimeException("expression system names cannot be set more than once"); 069 } 070 071 for (Map.Entry<String, String> entry : systemNames) { 072 FemaleDigitalExpressionSocket socket = 073 InstanceManager.getDefault(DigitalExpressionManager.class) 074 .createFemaleSocket(this, this, entry.getKey()); 075 076 _expressionEntries.add(new ExpressionEntry(socket, entry.getValue())); 077 } 078 } 079 080 public String getExpressionSystemName(int index) { 081 return _expressionEntries.get(index)._socketSystemName; 082 } 083 084 /** {@inheritDoc} */ 085 @Override 086 public Category getCategory() { 087 return Category.COMMON; 088 } 089 090 /** {@inheritDoc} */ 091 @Override 092 public boolean evaluate() throws JmriException { 093 boolean result = true; 094 for (ExpressionEntry e : _expressionEntries) { 095 if (e._socket.isConnected() && !e._socket.evaluate()) { 096 if (_type == Type.EvaluateNeeded) return false; 097 result = false; 098 } 099 } 100 return result; 101 } 102 103 @Override 104 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 105 return _expressionEntries.get(index)._socket; 106 } 107 108 @Override 109 public int getChildCount() { 110 return _expressionEntries.size(); 111 } 112 113 @Override 114 public String getShortDescription(Locale locale) { 115 return Bundle.getMessage(locale, "And_Short"); 116 } 117 118 @Override 119 public String getLongDescription(Locale locale) { 120 return Bundle.getMessage(locale, "And_Long", _type.toString()); 121 } 122 123 // This method ensures that we have enough of children 124 private void setNumSockets(int num) { 125 List<FemaleSocket> addList = new ArrayList<>(); 126 127 // Is there not enough children? 128 while (_expressionEntries.size() < num) { 129 FemaleDigitalExpressionSocket socket = 130 InstanceManager.getDefault(DigitalExpressionManager.class) 131 .createFemaleSocket(this, this, getNewSocketName()); 132 _expressionEntries.add(new ExpressionEntry(socket)); 133 addList.add(socket); 134 } 135 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 136 } 137 138 private void checkFreeSocket() { 139 boolean hasFreeSocket = false; 140 141 for (ExpressionEntry entry : _expressionEntries) { 142 hasFreeSocket |= !entry._socket.isConnected(); 143 } 144 if (!hasFreeSocket) { 145 FemaleDigitalExpressionSocket socket = 146 InstanceManager.getDefault(DigitalExpressionManager.class) 147 .createFemaleSocket(this, this, getNewSocketName()); 148 _expressionEntries.add(new ExpressionEntry(socket)); 149 150 List<FemaleSocket> list = new ArrayList<>(); 151 list.add(socket); 152 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list); 153 } 154 } 155 156 /** {@inheritDoc} */ 157 @Override 158 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 159 switch (oper) { 160 case Remove: // Possible if socket is not connected 161 return ! getChild(index).isConnected(); 162 case InsertBefore: 163 return true; // Always possible 164 case InsertAfter: 165 return true; // Always possible 166 case MoveUp: 167 return index > 0; // Possible if not first socket 168 case MoveDown: 169 return index+1 < getChildCount(); // Possible if not last socket 170 default: 171 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 172 } 173 } 174 175 private void insertNewSocket(int index) { 176 FemaleDigitalExpressionSocket socket = 177 InstanceManager.getDefault(DigitalExpressionManager.class) 178 .createFemaleSocket(this, this, getNewSocketName()); 179 _expressionEntries.add(index, new ExpressionEntry(socket)); 180 181 List<FemaleSocket> addList = new ArrayList<>(); 182 addList.add(socket); 183 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 184 } 185 186 private void removeSocket(int index) { 187 List<FemaleSocket> removeList = new ArrayList<>(); 188 removeList.add(_expressionEntries.remove(index)._socket); 189 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 190 } 191 192 private void moveSocketDown(int index) { 193 ExpressionEntry temp = _expressionEntries.get(index); 194 _expressionEntries.set(index, _expressionEntries.get(index+1)); 195 _expressionEntries.set(index+1, temp); 196 197 List<FemaleSocket> list = new ArrayList<>(); 198 list.add(_expressionEntries.get(index)._socket); 199 list.add(_expressionEntries.get(index)._socket); 200 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 201 } 202 203 /** {@inheritDoc} */ 204 @Override 205 public void doSocketOperation(int index, FemaleSocketOperation oper) { 206 switch (oper) { 207 case Remove: 208 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 209 removeSocket(index); 210 break; 211 case InsertBefore: 212 insertNewSocket(index); 213 break; 214 case InsertAfter: 215 insertNewSocket(index+1); 216 break; 217 case MoveUp: 218 if (index == 0) throw new UnsupportedOperationException("cannot move up first child"); 219 moveSocketDown(index-1); 220 break; 221 case MoveDown: 222 if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child"); 223 moveSocketDown(index); 224 break; 225 default: 226 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 227 } 228 } 229 230 @Override 231 public void connected(FemaleSocket socket) { 232 if (disableCheckForUnconnectedSocket) return; 233 234 for (ExpressionEntry entry : _expressionEntries) { 235 if (socket == entry._socket) { 236 entry._socketSystemName = 237 socket.getConnectedSocket().getSystemName(); 238 } 239 } 240 241 checkFreeSocket(); 242 } 243 244 @Override 245 public void disconnected(FemaleSocket socket) { 246 for (ExpressionEntry entry : _expressionEntries) { 247 if (socket == entry._socket) { 248 entry._socketSystemName = null; 249 break; 250 } 251 } 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public void setup() { 257 // We don't want to check for unconnected sockets while setup sockets 258 disableCheckForUnconnectedSocket = true; 259 260 for (ExpressionEntry ee : _expressionEntries) { 261 try { 262 if ( !ee._socket.isConnected() 263 || !ee._socket.getConnectedSocket().getSystemName() 264 .equals(ee._socketSystemName)) { 265 266 String socketSystemName = ee._socketSystemName; 267 ee._socket.disconnect(); 268 if (socketSystemName != null) { 269 MaleSocket maleSocket = 270 InstanceManager.getDefault(DigitalExpressionManager.class) 271 .getBySystemName(socketSystemName); 272 if (maleSocket != null) { 273 ee._socket.connect(maleSocket); 274 maleSocket.setup(); 275 } else { 276 log.error("cannot load digital expression {}", socketSystemName); 277 } 278 } 279 } else { 280 ee._socket.getConnectedSocket().setup(); 281 } 282 } catch (SocketAlreadyConnectedException ex) { 283 // This shouldn't happen and is a runtime error if it does. 284 throw new RuntimeException("socket is already connected"); 285 } 286 } 287 288 checkFreeSocket(); 289 290 disableCheckForUnconnectedSocket = false; 291 } 292 293 294 /* This class is public since ExpressionAndXml needs to access it. */ 295 private static class ExpressionEntry { 296 private String _socketSystemName; 297 private final FemaleDigitalExpressionSocket _socket; 298 299 private ExpressionEntry(FemaleDigitalExpressionSocket socket, String socketSystemName) { 300 _socketSystemName = socketSystemName; 301 _socket = socket; 302 } 303 304 private ExpressionEntry(FemaleDigitalExpressionSocket socket) { 305 this._socket = socket; 306 } 307 } 308 309 /** {@inheritDoc} */ 310 @Override 311 public void registerListenersForThisClass() { 312 // Do nothing 313 } 314 315 /** {@inheritDoc} */ 316 @Override 317 public void unregisterListenersForThisClass() { 318 // Do nothing 319 } 320 321 /** {@inheritDoc} */ 322 @Override 323 public void disposeMe() { 324 } 325 326 327 public enum Type { 328 /** 329 * All the connected sockets are evaluated. 330 */ 331 EvaluateAll(Bundle.getMessage("And_EvaluateAll")), 332 333 /** 334 * Evaluation starts with the first socket and continues until 335 * all sockets are evaluated or the result is known. 336 * For the And expression, it means that the evaluation stops 337 * as soon as a connected socket evaluates to false. 338 */ 339 EvaluateNeeded(Bundle.getMessage("And_EvaluateNeeded")); 340 341 private final String _text; 342 343 private Type(String text) { 344 this._text = text; 345 } 346 347 @Override 348 public String toString() { 349 return _text; 350 } 351 352 } 353 354 355 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(And.class); 356 357}