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