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 DigitalMany extends AbstractDigitalAction 018 implements FemaleSocketListener { 019 020 private final List<ActionEntry> _actionEntries = new ArrayList<>(); 021 private boolean disableCheckForUnconnectedSocket = false; 022 023 public DigitalMany(String sys, String user) 024 throws BadUserNameException, BadSystemNameException { 025 super(sys, user); 026 _actionEntries 027 .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class) 028 .createFemaleSocket(this, this, getNewSocketName()))); 029 } 030 031 public DigitalMany(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 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 040 String sysName = systemNames.get(getSystemName()); 041 String userName = userNames.get(getSystemName()); 042 if (sysName == null) sysName = manager.getAutoSystemName(); 043 DigitalMany copy = new DigitalMany(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 FemaleDigitalActionSocket socket = 056 InstanceManager.getDefault(DigitalActionManager.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(DigitalActionManager.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() throws JmriException { 115 for (ActionEntry actionEntry : _actionEntries) { 116 actionEntry._socket.execute(); 117 } 118 } 119 120 /** {@inheritDoc} */ 121 @Override 122 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 123 return _actionEntries.get(index)._socket; 124 } 125 126 /** {@inheritDoc} */ 127 @Override 128 public int getChildCount() { 129 return _actionEntries.size(); 130 } 131 132 // This method ensures that we have enough of children 133 private void setNumSockets(int num) { 134 List<FemaleSocket> addList = new ArrayList<>(); 135 136 // Is there not enough children? 137 while (_actionEntries.size() < num) { 138 FemaleDigitalActionSocket socket = 139 InstanceManager.getDefault(DigitalActionManager.class) 140 .createFemaleSocket(this, this, getNewSocketName()); 141 _actionEntries.add(new ActionEntry(socket)); 142 addList.add(socket); 143 } 144 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 145 } 146 147 private void checkFreeSocket() { 148 boolean hasFreeSocket = false; 149 150 for (ActionEntry entry : _actionEntries) { 151 hasFreeSocket |= !entry._socket.isConnected(); 152 } 153 if (!hasFreeSocket) { 154 FemaleDigitalActionSocket socket = 155 InstanceManager.getDefault(DigitalActionManager.class) 156 .createFemaleSocket(this, this, getNewSocketName()); 157 _actionEntries.add(new ActionEntry(socket)); 158 159 List<FemaleSocket> list = new ArrayList<>(); 160 list.add(socket); 161 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list); 162 } 163 } 164 165 /** {@inheritDoc} */ 166 @Override 167 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 168 switch (oper) { 169 case Remove: // Possible if socket is not connected and there are at least two sockets 170 if (_actionEntries.size() == 1) return false; 171 return ! getChild(index).isConnected(); 172 case InsertBefore: 173 return true; // Always possible 174 case InsertAfter: 175 return true; // Always possible 176 case MoveUp: 177 return index > 0; // Possible if not first socket 178 case MoveDown: 179 return index+1 < getChildCount(); // Possible if not last socket 180 default: 181 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 182 } 183 } 184 185 private void insertNewSocket(int index) { 186 FemaleDigitalActionSocket socket = 187 InstanceManager.getDefault(DigitalActionManager.class) 188 .createFemaleSocket(this, this, getNewSocketName()); 189 _actionEntries.add(index, new ActionEntry(socket)); 190 191 List<FemaleSocket> addList = new ArrayList<>(); 192 addList.add(socket); 193 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 194 } 195 196 private void removeSocket(int index) { 197 List<FemaleSocket> removeList = new ArrayList<>(); 198 removeList.add(_actionEntries.remove(index)._socket); 199 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 200 } 201 202 private void moveSocketDown(int index) { 203 ActionEntry temp = _actionEntries.get(index); 204 _actionEntries.set(index, _actionEntries.get(index+1)); 205 _actionEntries.set(index+1, temp); 206 207 List<FemaleSocket> list = new ArrayList<>(); 208 list.add(_actionEntries.get(index)._socket); 209 list.add(_actionEntries.get(index+1)._socket); 210 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 211 } 212 213 /** {@inheritDoc} */ 214 @Override 215 public void doSocketOperation(int index, FemaleSocketOperation oper) { 216 switch (oper) { 217 case Remove: 218 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 219 removeSocket(index); 220 break; 221 case InsertBefore: 222 insertNewSocket(index); 223 break; 224 case InsertAfter: 225 insertNewSocket(index+1); 226 break; 227 case MoveUp: 228 if (index == 0) throw new UnsupportedOperationException("cannot move up first child"); 229 moveSocketDown(index-1); 230 break; 231 case MoveDown: 232 if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child"); 233 moveSocketDown(index); 234 break; 235 default: 236 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 237 } 238 } 239 240 /** {@inheritDoc} */ 241 @Override 242 public void connected(FemaleSocket socket) { 243 if (disableCheckForUnconnectedSocket) return; 244 245 for (ActionEntry entry : _actionEntries) { 246 if (socket == entry._socket) { 247 entry._socketSystemName = 248 socket.getConnectedSocket().getSystemName(); 249 } 250 } 251 252 checkFreeSocket(); 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public void disconnected(FemaleSocket socket) { 258 for (ActionEntry entry : _actionEntries) { 259 if (socket == entry._socket) { 260 entry._socketSystemName = null; 261 break; 262 } 263 } 264 } 265 266 /** {@inheritDoc} */ 267 @Override 268 public String getShortDescription(Locale locale) { 269 return Bundle.getMessage(locale, "Many_Short"); 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 public String getLongDescription(Locale locale) { 275 return Bundle.getMessage(locale, "Many_Long"); 276 } 277 278 /** {@inheritDoc} */ 279 @Override 280 public void registerListenersForThisClass() { 281 // Do nothing 282 } 283 284 /** {@inheritDoc} */ 285 @Override 286 public void unregisterListenersForThisClass() { 287 // Do nothing 288 } 289 290 /** {@inheritDoc} */ 291 @Override 292 public void disposeMe() { 293 } 294 295 296 private static class ActionEntry { 297 private String _socketSystemName; 298 private final FemaleDigitalActionSocket _socket; 299 300 private ActionEntry(FemaleDigitalActionSocket socket, String socketSystemName) { 301 _socketSystemName = socketSystemName; 302 _socket = socket; 303 } 304 305 private ActionEntry(FemaleDigitalActionSocket socket) { 306 this._socket = socket; 307 } 308 309 } 310 311 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalMany.class); 312 313}