001package jmri.jmrit.logix; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import jmri.BeanSetting; 007 008import javax.annotation.Nonnull; 009import jmri.jmrit.logix.TrainOrder.Cause; 010 011 012/** 013 * A BlockOrder is a row in the route of the warrant. It contains 014 * where the warranted train enters a block, the path it takes and 015 * where it exits the block. (The route is a list of BlockOrder.) 016 * The Engineer is notified when the train enters the block. 017 * 018 * @author Pete Cressman Copyright (C) 2009 019 */ 020public class BlockOrder { 021 022 private static final Logger log = LoggerFactory.getLogger(BlockOrder.class); 023 024 private int _index; 025 private OBlock _block; // OBlock of these orders 026 private String _pathName; // path the train is to take in the block 027 private String _entryName; // Name of entry Portal 028 private String _exitName; // Name of exit Portal 029 private float _pathLength; // path length in millimeters 030 031 public BlockOrder(OBlock block) { 032 _block = block; 033 } 034 035 /** 036 * Create BlockOrder. 037 * 038 * @param block OBlock of this order 039 * @param path MUST be a path in the blocK 040 * @param entry MUST be a name of a Portal to the path 041 * @param exit MUST be a name of a Portal to the path 042 */ 043 public BlockOrder(OBlock block, String path, String entry, String exit) { 044 this(block); 045 _pathName = path; 046 _entryName = entry; 047 _exitName = exit; 048 } 049 050 // for use by WarrantTableFrame 051 protected BlockOrder(BlockOrder bo) { 052 _index = bo._index; 053 _block = bo._block; // shallow copy OK. WarrantTableFrame doesn't write to block 054 _pathName = bo._pathName; 055 _entryName = bo._entryName; 056 _exitName = bo._exitName; 057 } 058 059 public void setIndex(int idx) { 060 _index = idx; 061 } 062 063 public int getIndex() { 064 return _index; 065 } 066 067 protected void setEntryName(String name) { 068 _entryName = name; 069 } 070 071 public String getEntryName() { 072 return _entryName; 073 } 074 075 protected void setExitName(String name) { 076 _exitName = name; 077 } 078 079 public String getExitName() { 080 return _exitName; 081 } 082 083 /** 084 * Set Path. Note that the Path's 'fromPortal' and 'toPortal' have no 085 * bearing on the BlockOrder's entryPortal and exitPortal. 086 * @param path Name of the OPath connecting the entry and exit Portals 087 */ 088 protected void setPathName(String path) { 089 _pathName = path; 090 } 091 092 public String getPathName() { 093 return _pathName; 094 } 095 096 protected OPath getPath() { 097 return _block.getPathByName(_pathName); 098 } 099 100 protected String setPath(Warrant warrant) { 101 String msg = _block.setPath(getPathName(), warrant); 102 if (msg == null) { 103 Portal p = getEntryPortal(); 104 if (p != null) { 105 p.setEntryState(_block); 106 } 107 } 108 return msg; 109 } 110 111 @Nonnull 112 protected TrainOrder allocatePaths(Warrant warrant, boolean allocate) { 113 if (_pathName == null) { 114 log.error("setPaths({}) - {}", warrant.getDisplayName(), Bundle.getMessage("NoPaths", _block.getDisplayName())); 115 return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, 116 Bundle.getMessage("NoPaths", _block.getDisplayName())); 117 } 118 if (log.isDebugEnabled()) { 119 log.debug("{}: calls allocatePaths() in block \"{}\" for path \"{}\". _index={}", 120 warrant.getDisplayName(), _block.getDisplayName(), _pathName, _index); 121 } 122 TrainOrder to = findStopCondition(this, warrant); 123 if (to != null && Warrant.Stop.equals(to._speedType)) { 124 return to; 125 } 126 String msg = _block.allocate(warrant); 127 if (msg != null) { // unnecessary, findStopCondition() already has checked 128 return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, msg); 129 } 130 131 // Check if next block can be allocated 132 BlockOrder bo1 = warrant.getBlockOrderAt(_index + 1); 133 if (bo1 != null) { 134 OBlock nextBlock = bo1.getBlock(); 135 TrainOrder to1 = findStopCondition(bo1, warrant); 136 if (to1 == null || !Warrant.Stop.equals(to1._speedType)) { // Train may enter block of bo1 137 if (allocate) { 138 nextBlock.allocate(warrant); 139 nextBlock.showAllocated(warrant, bo1._pathName); 140 } 141 } else { 142 // See if path to exit can be set without messing up block of bo1 143 OPath path1 = getPath(); 144 Portal exit = getExitPortal(); 145 msg = pathsConnect(path1, exit, bo1._block); 146 } 147 if (msg != null) { 148 // cannot set path 149 return new TrainOrder(Warrant.Stop, (to1 != null?to1._cause:Cause.WARRANT), bo1._index, _index, msg); 150 } 151 // Crossovers typically have both switches controlled by one TO, 152 // yet each switch is in a different block. Setting the path may change 153 // a shared TO for another warrant and change its path to 154 // short or derail its train entering the block. So we may not allow 155 // this warrant to set the path in bo1. 156 // Because the path in bo1 cannot be set, it is not safe to enter 157 // the next block. The warrant must hold the train in this block. 158 // However, the path in this block may be set 159 } 160 if (allocate) { 161 msg = setPath(warrant); 162 if (msg != null) { // unnecessary, already been checked 163 return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, msg); 164 } 165 } 166 if (to != null) { 167 return to; 168 } 169 return new TrainOrder(null, Cause.NONE, _index, _index, null); 170 } 171 172 private TrainOrder findStopCondition(BlockOrder bo, Warrant warrant) { 173 OBlock block = bo.getBlock(); 174 Warrant w = block.getWarrant(); 175 if (w != null && !warrant.equals(w)) { 176 return new TrainOrder(Warrant.Stop, Cause.WARRANT, bo._index, bo._index, 177 Bundle.getMessage("AllocatedToWarrant", 178 w.getDisplayName(), block.getDisplayName(), w.getTrainName())); 179 } 180 if (block.isOccupied()) { 181 String rogue = (String)block.getValue(); 182 if (rogue == null) { 183 rogue = Bundle.getMessage("unknownTrain"); 184 } 185 if (!rogue.equals(warrant.getTrainName())) { 186 return new TrainOrder(Warrant.Stop, Cause.OCCUPY, bo._index, bo._index, 187 Bundle.getMessage("blockInUse", rogue, block.getDisplayName())); 188 } 189 } 190 String speedType = getPermissibleSpeedAt(bo); 191 if (speedType != null) { 192 String msg; 193 if (Warrant.Stop.equals(speedType)) { 194 msg = Bundle.getMessage("BlockStopAspect", block.getDisplayName(), speedType); 195 } else { 196 msg = Bundle.getMessage("BlockSpeedAspect", block.getDisplayName(), speedType); 197 } 198 return new TrainOrder(speedType, Cause.SIGNAL, bo._index, bo._index, msg); 199 } 200 return null; 201 } 202 203 protected String pathsConnect(OPath path1, Portal exit, OBlock block) { 204 if (exit == null || block == null) { 205 return null; 206 } 207 208 OPath path2 = block.getPath(); 209 if (path2 == null) { 210 return null; 211 } 212 for (BeanSetting bs1 : path1.getSettings()) { 213 for (BeanSetting bs2 : path2.getSettings()) { 214 if (bs1.getBean().equals(bs2.getBean())) { 215 // TO is shared (same bean) 216 if (bs1.equals(bs2)) { 217 if (log.isDebugEnabled()) { 218 log.debug("Path \"{}\" in block \"{}\" and \"{}\" in block \"{}\" agree on setting of shared turnout \"{}\"", 219 path1.getName(), _block.getDisplayName(), path2.getName(), block.getDisplayName(), 220 bs1.getBean().getDisplayName()); 221 } 222 return Bundle.getMessage("SharedTurnout", bs1.getBean().getDisplayName(), _block.getDisplayName(), block.getDisplayName()); 223 } else { 224 if (log.isDebugEnabled()) { 225 log.debug("Path \"{}\" in block \"{}\" and \"{}\" in block \"{}\" have opposed settings of shared turnout \"{}\"", 226 path1.getName(), _block.getDisplayName(), path2.getName(), block.getDisplayName(), 227 bs1.getBean().getDisplayName()); 228 } 229 return Bundle.getMessage("SharedTurnout", bs1.getBean().getDisplayName(), _block.getDisplayName(), block.getDisplayName()); 230 } 231 } 232 } 233 } 234 return null; 235 } 236 237 static protected String getPermissibleSpeedAt(BlockOrder bo) { 238 String speedType = bo.getPermissibleEntranceSpeed(); 239 if (speedType != null) { 240 if (log.isDebugEnabled()){ 241 log.debug("getPermissibleSpeedAt(): \"{}\" Signal speed= {}", 242 bo._block.getDisplayName(), speedType); 243 } 244 } else { // if signal is configured, ignore block 245 speedType = bo._block.getBlockSpeed(); 246 if (speedType.equals("")) { 247 speedType = null; 248 } 249 if (log.isDebugEnabled()) { 250 if (speedType != null) { 251 log.debug("getPermissibleSpeedAt(): \"{}\" Block speed= {}", 252 bo._block.getDisplayName(), speedType); 253 } 254 } 255 } 256 return speedType; 257 } 258 259 protected void setPathLength(float len) { 260 _pathLength = len; 261 } 262 263 protected float getPathLength() { 264 if (_pathLength <= 0) { 265 OPath p = getPath(); 266 if (p != null) { 267 _pathLength = p.getLengthMm(); 268 } else { 269 _pathLength = 0; 270 } 271 } 272 return _pathLength; 273 } 274 275 protected void setBlock(OBlock block) { 276 _block = block; 277 } 278 279 public OBlock getBlock() { 280 return _block; 281 } 282 283 protected Portal getEntryPortal() { 284 if (_entryName == null) { 285 return null; 286 } 287 return _block.getPortalByName(_entryName); 288 } 289 290 protected Portal getExitPortal() { 291 if (_exitName == null) { 292 return null; 293 } 294 return _block.getPortalByName(_exitName); 295 } 296 297 /** 298 * Check signals for entrance into next block. 299 * 300 * @return speed 301 */ 302 protected String getPermissibleEntranceSpeed() { 303 Portal portal = _block.getPortalByName(getEntryName()); 304 if (portal != null) { 305 return portal.getPermissibleSpeed(_block, true); 306 } 307 return null; 308 } 309 310 protected float getEntranceSpace() { 311 Portal portal = _block.getPortalByName(getEntryName()); 312 if (portal != null) { 313 return portal.getEntranceSpaceForBlock(_block); 314 } 315 return 0; 316 } 317 318 /** 319 * Get the signal protecting entry into the block of this blockorder 320 * @return signal 321 */ 322 protected jmri.NamedBean getSignal() { 323 Portal portal = getEntryPortal(); 324 if (portal != null) { 325 return portal.getSignalProtectingBlock(_block); 326 } 327 return null; 328 } 329 330 @Override 331 public String toString() { 332 StringBuilder sb = new StringBuilder("BlockOrder: Block \""); 333 sb.append( _block.getDisplayName()); 334 sb.append("\" has Path \""); 335 sb.append(_pathName); 336 sb.append("\" with Portals, entry= \""); 337 sb.append(_entryName); 338 sb.append("\" and exit= \""); 339 sb.append(_exitName); 340 sb.append("\""); 341 return sb.toString(); 342 } 343}