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