001package jmri; 002 003import java.beans.PropertyChangeEvent; 004import java.io.IOException; 005import java.time.Instant; 006import java.util.ArrayList; 007import java.util.List; 008import javax.annotation.CheckForNull; 009import javax.annotation.CheckReturnValue; 010import javax.annotation.Nonnull; 011 012import jmri.implementation.AbstractShutDownTask; 013import jmri.implementation.SignalSpeedMap; 014import jmri.jmrit.display.layoutEditor.BlockValueFile; 015import jmri.managers.AbstractManager; 016 017/** 018 * Basic implementation of a BlockManager. 019 * <p> 020 * Note that this does not enforce any particular system naming convention. 021 * <p> 022 * Note this is a concrete class, unlike the interface/implementation pairs of 023 * most Managers, because there are currently only one implementation for 024 * Blocks. 025 * <hr> 026 * This file is part of JMRI. 027 * <p> 028 * JMRI is free software; you can redistribute it and/or modify it under the 029 * terms of version 2 of the GNU General Public License as published by the Free 030 * Software Foundation. See the "COPYING" file for a copy of this license. 031 * <p> 032 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 033 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 034 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 035 * 036 * @author Bob Jacobsen Copyright (C) 2006 037 */ 038public class BlockManager extends AbstractManager<Block> 039 implements ProvidingManager<Block>, InstanceManagerAutoDefault { 040 041 private final String powerManagerChangeName; 042 public final ShutDownTask shutDownTask = new AbstractShutDownTask("Writing Blocks") { 043 @Override 044 public void run() { 045 try { 046 new BlockValueFile().writeBlockValues(); 047 } catch (IOException ex) { 048 log.error("Exception writing blocks", ex); 049 } 050 } 051 }; 052 053 public BlockManager() { 054 super(); 055 InstanceManager.getDefault(SensorManager.class).addVetoableChangeListener(BlockManager.this); 056 InstanceManager.getDefault(ReporterManager.class).addVetoableChangeListener(BlockManager.this); 057 InstanceManager.getList(PowerManager.class).forEach(pm -> pm.addPropertyChangeListener(BlockManager.this)); 058 powerManagerChangeName = InstanceManager.getListPropertyName(PowerManager.class); 059 InstanceManager.addPropertyChangeListener(BlockManager.this); 060 InstanceManager.getDefault(ShutDownManager.class).register(shutDownTask); 061 } 062 063 @Override 064 public void dispose() { 065 InstanceManager.getDefault(SensorManager.class).removeVetoableChangeListener(this); 066 InstanceManager.getDefault(ReporterManager.class).removeVetoableChangeListener(this); 067 InstanceManager.getList(PowerManager.class).forEach(pm -> pm.removePropertyChangeListener(this)); 068 InstanceManager.removePropertyChangeListener(this); 069 super.dispose(); 070 InstanceManager.getDefault(ShutDownManager.class).deregister(shutDownTask); 071 } 072 073 /** 074 * String constant for property Default Block Speed Change 075 */ 076 public static final String PROPERTY_DEFAULT_BLOCK_SPEED_CHANGE = "DefaultBlockSpeedChange"; 077 078 @Override 079 @CheckReturnValue 080 public int getXMLOrder() { 081 return Manager.BLOCKS; 082 } 083 084 @Override 085 @CheckReturnValue 086 public char typeLetter() { 087 return 'B'; 088 } 089 090 @Override 091 public Class<Block> getNamedBeanClass() { 092 return Block.class; 093 } 094 095 private boolean saveBlockPath = true; 096 097 @CheckReturnValue 098 public boolean isSavedPathInfo() { 099 return saveBlockPath; 100 } 101 102 public void setSavedPathInfo(boolean save) { 103 saveBlockPath = save; 104 } 105 106 /** 107 * Create a new Block, only if it does not exist. 108 * 109 * @param systemName the system name 110 * @param userName the user name 111 * @return null if a Block with the same systemName or userName already 112 * exists, or if there is trouble creating a new Block 113 */ 114 @CheckForNull 115 public Block createNewBlock(@Nonnull String systemName, @CheckForNull String userName) { 116 // Check that Block does not already exist 117 Block r; 118 if (userName != null && !userName.isEmpty()) { 119 r = getByUserName(userName); 120 if (r != null) { 121 return null; 122 } 123 } 124 r = getBySystemName(systemName); 125 if (r != null) { 126 return null; 127 } 128 // Block does not exist, create a new Block 129 r = new Block(systemName, userName); 130 131 // Keep track of the last created auto system name 132 updateAutoNumber(systemName); 133 134 // save in the maps 135 register(r); 136 try { 137 r.setBlockSpeed("Global"); // NOI18N 138 } catch (JmriException ex) { 139 log.error("Unexpected exception {}", ex.getMessage()); 140 } 141 return r; 142 } 143 144 /** 145 * Create a new Block using an automatically incrementing system 146 * name. 147 * 148 * @param userName the user name for the new Block 149 * @return null if a Block with the same systemName or userName already 150 * exists, or if there is trouble creating a new Block. 151 */ 152 @CheckForNull 153 public Block createNewBlock(@CheckForNull String userName) { 154 return createNewBlock(getAutoSystemName(), userName); 155 } 156 157 /** 158 * If the Block exists, return it, otherwise create a new one and return it. 159 * If the argument starts with the system prefix and type letter, usually 160 * "IB", then the argument is considered a system name, otherwise it's 161 * considered a user name and a system name is automatically created. 162 * 163 * @param name the system name or the user name for the block 164 * @return a new or existing Block 165 * @throws IllegalArgumentException if cannot create block or no name supplied; never returns null 166 */ 167 @Nonnull 168 public Block provideBlock(@Nonnull String name) { 169 if (name.isEmpty()) { 170 throw new IllegalArgumentException("Could not create block, no name supplied"); 171 } 172 Block b = getBlock(name); 173 if (b != null) { 174 return b; 175 } 176 if (name.startsWith(getSystemNamePrefix())) { 177 b = createNewBlock(name, null); 178 } else { 179 b = createNewBlock(name); 180 } 181 if (b == null) { 182 throw new IllegalArgumentException("Could not create block \"" + name + "\""); 183 } 184 return b; 185 } 186 187 /** 188 * Get an existing Block. First looks up assuming that name is a 189 * User Name. If this fails looks up assuming that name is a System Name. If 190 * both fail, returns null. 191 * 192 * @param name the name of an existing block 193 * @return a Block or null if none found 194 */ 195 @CheckReturnValue 196 @CheckForNull 197 public Block getBlock(@Nonnull String name) { 198 Block r = getByUserName(name); 199 if (r != null) { 200 return r; 201 } 202 return getBySystemName(name); 203 } 204 205 @CheckReturnValue 206 @CheckForNull 207 public Block getByDisplayName(@Nonnull String key) { 208 // First try to find it in the user list. 209 // If that fails, look it up in the system list 210 Block retv = this.getByUserName(key); 211 if (retv == null) { 212 retv = this.getBySystemName(key); 213 } 214 // If it's not in the system list, go ahead and return null 215 return retv; 216 } 217 218 private String defaultSpeed = "Normal"; 219 220 /** 221 * Set the Default Block Speed. 222 * @param speed the speed 223 * @throws IllegalArgumentException if provided speed is invalid 224 */ 225 public void setDefaultSpeed(@Nonnull String speed) { 226 if (defaultSpeed.equals(speed)) { 227 return; 228 } 229 230 try { 231 Float.valueOf(speed); 232 } catch (NumberFormatException nx) { 233 try { 234 InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(speed); 235 } catch (IllegalArgumentException ex) { 236 throw new IllegalArgumentException("Value of requested default block speed \"" 237 + speed + "\" is not valid", ex); 238 } 239 } 240 String oldSpeed = defaultSpeed; 241 defaultSpeed = speed; 242 firePropertyChange(PROPERTY_DEFAULT_BLOCK_SPEED_CHANGE, oldSpeed, speed); 243 } 244 245 @CheckReturnValue 246 @Nonnull 247 public String getDefaultSpeed() { 248 return defaultSpeed; 249 } 250 251 @Override 252 @CheckReturnValue 253 @Nonnull 254 public String getBeanTypeHandled(boolean plural) { 255 return Bundle.getMessage(plural ? "BeanNameBlocks" : "BeanNameBlock"); 256 } 257 258 /** 259 * Get a list of blocks which the supplied roster entry appears to be 260 * occupying. A block is assumed to contain this roster entry if its value 261 * is the RosterEntry itself, or a string with the entry's id or dcc 262 * address. 263 * 264 * @param re the roster entry 265 * @return list of block system names 266 */ 267 @CheckReturnValue 268 @Nonnull 269 public List<Block> getBlocksOccupiedByRosterEntry(@Nonnull BasicRosterEntry re) { 270 List<Block> blockList = new ArrayList<>(); 271 getNamedBeanSet().stream().forEach(b -> { 272 if (b != null) { 273 Object obj = b.getValue(); 274 if ( obj != null && blockValueEqualsRosterEntry(obj, re)) { 275 blockList.add(b); 276 } 277 } 278 }); 279 return blockList; 280 } 281 282 private boolean blockValueEqualsRosterEntry( @Nonnull Object obj, @Nonnull BasicRosterEntry re ){ 283 return ( obj instanceof BasicRosterEntry && obj == re) || 284 obj.toString().equals(re.getId()) || 285 obj.toString().equals(re.getDccAddress()); 286 } 287 288 private Instant lastTimeLayoutPowerOn; // the most recent time any power manager had a power ON event 289 290 /** 291 * Listen for changes to the power state from any power managers 292 * in use in order to track how long it's been since power was applied 293 * to the layout. This information is used in {@link Block#goingActive()} 294 * when deciding whether to restore a block's last value. 295 * 296 * Also listen for additions/removals or PowerManagers 297 * 298 * @param e the change event 299 */ 300 @Override 301 public void propertyChange(PropertyChangeEvent e) { 302 super.propertyChange(e); 303 if ( PowerManager.POWER.equals(e.getPropertyName())) { 304 try { 305 PowerManager pm = (PowerManager) e.getSource(); 306 if (pm.getPower() == PowerManager.ON) { 307 lastTimeLayoutPowerOn = Instant.now(); 308 } 309 } catch (NoSuchMethodError xe) { 310 // do nothing 311 } 312 } 313 if (powerManagerChangeName.equals(e.getPropertyName())) { 314 if (e.getNewValue() == null) { 315 // powermanager has been removed 316 PowerManager pm = (PowerManager) e.getOldValue(); 317 pm.removePropertyChangeListener(this); 318 } else { 319 // a powermanager has been added 320 PowerManager pm = (PowerManager) e.getNewValue(); 321 pm.addPropertyChangeListener(this); 322 } 323 } 324 } 325 326 /** 327 * Get the amount of time since the layout was last powered up, 328 * in milliseconds. If the layout has not been powered up as far as 329 * JMRI knows it returns a very long time indeed. 330 * 331 * @return long int 332 */ 333 public long timeSinceLastLayoutPowerOn() { 334 if (lastTimeLayoutPowerOn == null) { 335 return Long.MAX_VALUE; 336 } 337 return Instant.now().toEpochMilli() - lastTimeLayoutPowerOn.toEpochMilli(); 338 } 339 340 @Override 341 @Nonnull 342 public Block provide(@Nonnull String name) { 343 return provideBlock(name); 344 } 345 346 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockManager.class); 347 348}