001package jmri.jmrit.logixng.actions; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Locale; 006import java.util.Map; 007 008import jmri.InstanceManager; 009import jmri.JmriException; 010import jmri.jmrit.logixng.*; 011 012/** 013 * Execute many Actions in a specific order. 014 * 015 * @author Daniel Bergqvist Copyright 2018 016 */ 017public class DigitalBooleanMany extends AbstractDigitalBooleanAction 018 implements FemaleSocketListener { 019 020 private final List<ActionEntry> _actionEntries = new ArrayList<>(); 021 private boolean disableCheckForUnconnectedSocket = false; 022 023 public DigitalBooleanMany(String sys, String user) 024 throws BadUserNameException, BadSystemNameException { 025 super(sys, user); 026 _actionEntries 027 .add(new ActionEntry(InstanceManager.getDefault(DigitalBooleanActionManager.class) 028 .createFemaleSocket(this, this, getNewSocketName()))); 029 } 030 031 public DigitalBooleanMany(String sys, String user, List<Map.Entry<String, String>> actionSystemNames) 032 throws BadUserNameException, BadSystemNameException { 033 super(sys, user); 034 setActionSystemNames(actionSystemNames); 035 } 036 037 @Override 038 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 039 DigitalBooleanActionManager manager = InstanceManager.getDefault(DigitalBooleanActionManager.class); 040 String sysName = systemNames.get(getSystemName()); 041 String userName = userNames.get(getSystemName()); 042 if (sysName == null) sysName = manager.getAutoSystemName(); 043 DigitalBooleanMany copy = new DigitalBooleanMany(sysName, userName); 044 copy.setComment(getComment()); 045 copy.setNumSockets(getChildCount()); 046 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 047 } 048 049 private void setActionSystemNames(List<Map.Entry<String, String>> systemNames) { 050 if (!_actionEntries.isEmpty()) { 051 throw new RuntimeException("action system names cannot be set more than once"); 052 } 053 054 for (Map.Entry<String, String> entry : systemNames) { 055 FemaleDigitalBooleanActionSocket socket = 056 InstanceManager.getDefault(DigitalBooleanActionManager.class) 057 .createFemaleSocket(this, this, entry.getKey()); 058 059 _actionEntries.add(new ActionEntry(socket, entry.getValue())); 060 } 061 } 062 063 public String getActionSystemName(int index) { 064 return _actionEntries.get(index)._socketSystemName; 065 } 066 067 /** {@inheritDoc} */ 068 @Override 069 public void setup() { 070 // We don't want to check for unconnected sockets while setup sockets 071 disableCheckForUnconnectedSocket = true; 072 073 for (ActionEntry ae : _actionEntries) { 074 try { 075 if ( !ae._socket.isConnected() 076 || !ae._socket.getConnectedSocket().getSystemName() 077 .equals(ae._socketSystemName)) { 078 079 String socketSystemName = ae._socketSystemName; 080 ae._socket.disconnect(); 081 if (socketSystemName != null) { 082 MaleSocket maleSocket = 083 InstanceManager.getDefault(DigitalBooleanActionManager.class) 084 .getBySystemName(socketSystemName); 085 if (maleSocket != null) { 086 ae._socket.connect(maleSocket); 087 maleSocket.setup(); 088 } else { 089 log.error("cannot load digital action {}", socketSystemName); 090 } 091 } 092 } else { 093 ae._socket.getConnectedSocket().setup(); 094 } 095 } catch (SocketAlreadyConnectedException ex) { 096 // This shouldn't happen and is a runtime error if it does. 097 throw new RuntimeException("socket is already connected"); 098 } 099 } 100 101// checkFreeSocket(); 102 103 disableCheckForUnconnectedSocket = false; 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public Category getCategory() { 109 return Category.COMMON; 110 } 111 112 /** {@inheritDoc} */ 113 @Override 114 public void execute(boolean value) throws JmriException { 115 for (ActionEntry actionEntry : _actionEntries) { 116 actionEntry._socket.execute(value); 117 } 118 } 119 120 @Override 121 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 122 return _actionEntries.get(index)._socket; 123 } 124 125 @Override 126 public int getChildCount() { 127 return _actionEntries.size(); 128 } 129 130 // This method ensures that we have enough of children 131 private void setNumSockets(int num) { 132 List<FemaleSocket> addList = new ArrayList<>(); 133 134 // Is there not enough children? 135 while (_actionEntries.size() < num) { 136 FemaleDigitalBooleanActionSocket socket = 137 InstanceManager.getDefault(DigitalBooleanActionManager.class) 138 .createFemaleSocket(this, this, getNewSocketName()); 139 _actionEntries.add(new ActionEntry(socket)); 140 addList.add(socket); 141 } 142 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 143 } 144 145 private void checkFreeSocket() { 146 boolean hasFreeSocket = false; 147 148 for (ActionEntry entry : _actionEntries) { 149 hasFreeSocket |= !entry._socket.isConnected(); 150 } 151 if (!hasFreeSocket) { 152 FemaleDigitalBooleanActionSocket socket = 153 InstanceManager.getDefault(DigitalBooleanActionManager.class) 154 .createFemaleSocket(this, this, getNewSocketName()); 155 _actionEntries.add(new ActionEntry(socket)); 156 157 List<FemaleSocket> list = new ArrayList<>(); 158 list.add(socket); 159 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list); 160 } 161 } 162 163 /** {@inheritDoc} */ 164 @Override 165 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 166 switch (oper) { 167 case Remove: // Possible if socket is not connected 168 return ! getChild(index).isConnected(); 169 case InsertBefore: 170 return true; // Always possible 171 case InsertAfter: 172 return true; // Always possible 173 case MoveUp: 174 return index > 0; // Possible if not first socket 175 case MoveDown: 176 return index+1 < getChildCount(); // Possible if not last socket 177 default: 178 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 179 } 180 } 181 182 private void insertNewSocket(int index) { 183 FemaleDigitalBooleanActionSocket socket = 184 InstanceManager.getDefault(DigitalBooleanActionManager.class) 185 .createFemaleSocket(this, this, getNewSocketName()); 186 _actionEntries.add(index, new ActionEntry(socket)); 187 188 List<FemaleSocket> addList = new ArrayList<>(); 189 addList.add(socket); 190 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 191 } 192 193 private void removeSocket(int index) { 194 List<FemaleSocket> removeList = new ArrayList<>(); 195 removeList.add(_actionEntries.remove(index)._socket); 196 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 197 } 198 199 private void moveSocketDown(int index) { 200 ActionEntry temp = _actionEntries.get(index); 201 _actionEntries.set(index, _actionEntries.get(index+1)); 202 _actionEntries.set(index+1, temp); 203 204 List<FemaleSocket> list = new ArrayList<>(); 205 list.add(_actionEntries.get(index)._socket); 206 list.add(_actionEntries.get(index+1)._socket); 207 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 208 } 209 210 /** {@inheritDoc} */ 211 @Override 212 public void doSocketOperation(int index, FemaleSocketOperation oper) { 213 switch (oper) { 214 case Remove: 215 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 216 removeSocket(index); 217 break; 218 case InsertBefore: 219 insertNewSocket(index); 220 break; 221 case InsertAfter: 222 insertNewSocket(index+1); 223 break; 224 case MoveUp: 225 if (index == 0) throw new UnsupportedOperationException("cannot move up first child"); 226 moveSocketDown(index-1); 227 break; 228 case MoveDown: 229 if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child"); 230 moveSocketDown(index); 231 break; 232 default: 233 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 234 } 235 } 236 237 @Override 238 public void connected(FemaleSocket socket) { 239 if (disableCheckForUnconnectedSocket) return; 240 241 for (ActionEntry entry : _actionEntries) { 242 if (socket == entry._socket) { 243 entry._socketSystemName = 244 socket.getConnectedSocket().getSystemName(); 245 } 246 } 247 248 checkFreeSocket(); 249 } 250 251 @Override 252 public void disconnected(FemaleSocket socket) { 253 for (ActionEntry entry : _actionEntries) { 254 if (socket == entry._socket) { 255 entry._socketSystemName = null; 256 break; 257 } 258 } 259 } 260 261 @Override 262 public String getShortDescription(Locale locale) { 263 return Bundle.getMessage(locale, "Many_Short"); 264 } 265 266 @Override 267 public String getLongDescription(Locale locale) { 268 return Bundle.getMessage(locale, "Many_Long"); 269 } 270 271 /** {@inheritDoc} */ 272 @Override 273 public void registerListenersForThisClass() { 274 // Do nothing 275 } 276 277 /** {@inheritDoc} */ 278 @Override 279 public void unregisterListenersForThisClass() { 280 // Do nothing 281 } 282 283 /** {@inheritDoc} */ 284 @Override 285 public void disposeMe() { 286 } 287 288 289 private static class ActionEntry { 290 private String _socketSystemName; 291 private final FemaleDigitalBooleanActionSocket _socket; 292 293 private ActionEntry(FemaleDigitalBooleanActionSocket socket, String socketSystemName) { 294 _socketSystemName = socketSystemName; 295 _socket = socket; 296 } 297 298 private ActionEntry(FemaleDigitalBooleanActionSocket socket) { 299 this._socket = socket; 300 } 301 302 } 303 304 305 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalBooleanMany.class); 306 307}