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 @Override 074 @CheckReturnValue 075 public int getXMLOrder() { 076 return Manager.BLOCKS; 077 } 078 079 @Override 080 @CheckReturnValue 081 public char typeLetter() { 082 return 'B'; 083 } 084 085 @Override 086 public Class<Block> getNamedBeanClass() { 087 return Block.class; 088 } 089 090 private boolean saveBlockPath = true; 091 092 @CheckReturnValue 093 public boolean isSavedPathInfo() { 094 return saveBlockPath; 095 } 096 097 public void setSavedPathInfo(boolean save) { 098 saveBlockPath = save; 099 } 100 101 /** 102 * Create a new Block, only if it does not exist. 103 * 104 * @param systemName the system name 105 * @param userName the user name 106 * @return null if a Block with the same systemName or userName already 107 * exists, or if there is trouble creating a new Block 108 */ 109 @CheckForNull 110 public Block createNewBlock(@Nonnull String systemName, @CheckForNull String userName) { 111 // Check that Block does not already exist 112 Block r; 113 if (userName != null && !userName.isEmpty()) { 114 r = getByUserName(userName); 115 if (r != null) { 116 return null; 117 } 118 } 119 r = getBySystemName(systemName); 120 if (r != null) { 121 return null; 122 } 123 // Block does not exist, create a new Block 124 r = new Block(systemName, userName); 125 126 // Keep track of the last created auto system name 127 updateAutoNumber(systemName); 128 129 // save in the maps 130 register(r); 131 try { 132 r.setBlockSpeed("Global"); // NOI18N 133 } catch (JmriException ex) { 134 log.error("Unexpected exception {}", ex.getMessage()); 135 } 136 return r; 137 } 138 139 /** 140 * Create a new Block using an automatically incrementing system 141 * name. 142 * 143 * @param userName the user name for the new Block 144 * @return null if a Block with the same systemName or userName already 145 * exists, or if there is trouble creating a new Block. 146 */ 147 @CheckForNull 148 public Block createNewBlock(@CheckForNull String userName) { 149 return createNewBlock(getAutoSystemName(), userName); 150 } 151 152 /** 153 * If the Block exists, return it, otherwise create a new one and return it. 154 * If the argument starts with the system prefix and type letter, usually 155 * "IB", then the argument is considered a system name, otherwise it's 156 * considered a user name and a system name is automatically created. 157 * 158 * @param name the system name or the user name for the block 159 * @return a new or existing Block 160 * @throws IllegalArgumentException if cannot create block or no name supplied; never returns null 161 */ 162 @Nonnull 163 public Block provideBlock(@Nonnull String name) { 164 if (name.isEmpty()) { 165 throw new IllegalArgumentException("Could not create block, no name supplied"); 166 } 167 Block b = getBlock(name); 168 if (b != null) { 169 return b; 170 } 171 if (name.startsWith(getSystemNamePrefix())) { 172 b = createNewBlock(name, null); 173 } else { 174 b = createNewBlock(name); 175 } 176 if (b == null) { 177 throw new IllegalArgumentException("Could not create block \"" + name + "\""); 178 } 179 return b; 180 } 181 182 /** 183 * Get an existing Block. First looks up assuming that name is a 184 * User Name. If this fails looks up assuming that name is a System Name. If 185 * both fail, returns null. 186 * 187 * @param name the name of an existing block 188 * @return a Block or null if none found 189 */ 190 @CheckReturnValue 191 @CheckForNull 192 public Block getBlock(@Nonnull String name) { 193 Block r = getByUserName(name); 194 if (r != null) { 195 return r; 196 } 197 return getBySystemName(name); 198 } 199 200 @CheckReturnValue 201 @CheckForNull 202 public Block getByDisplayName(@Nonnull String key) { 203 // First try to find it in the user list. 204 // If that fails, look it up in the system list 205 Block retv = this.getByUserName(key); 206 if (retv == null) { 207 retv = this.getBySystemName(key); 208 } 209 // If it's not in the system list, go ahead and return null 210 return retv; 211 } 212 213 private String defaultSpeed = "Normal"; 214 215 /** 216 * Set the Default Block Speed. 217 * @param speed the speed 218 * @throws IllegalArgumentException if provided speed is invalid 219 */ 220 public void setDefaultSpeed(@Nonnull String speed) { 221 if (defaultSpeed.equals(speed)) { 222 return; 223 } 224 225 try { 226 Float.valueOf(speed); 227 } catch (NumberFormatException nx) { 228 try { 229 InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(speed); 230 } catch (IllegalArgumentException ex) { 231 throw new IllegalArgumentException("Value of requested default block speed \"" 232 + speed + "\" is not valid", ex); 233 } 234 } 235 String oldSpeed = defaultSpeed; 236 defaultSpeed = speed; 237 firePropertyChange("DefaultBlockSpeedChange", oldSpeed, speed); 238 } 239 240 @CheckReturnValue 241 @Nonnull 242 public String getDefaultSpeed() { 243 return defaultSpeed; 244 } 245 246 @Override 247 @CheckReturnValue 248 @Nonnull 249 public String getBeanTypeHandled(boolean plural) { 250 return Bundle.getMessage(plural ? "BeanNameBlocks" : "BeanNameBlock"); 251 } 252 253 /** 254 * Get a list of blocks which the supplied roster entry appears to be 255 * occupying. A block is assumed to contain this roster entry if its value 256 * is the RosterEntry itself, or a string with the entry's id or dcc 257 * address. 258 * 259 * @param re the roster entry 260 * @return list of block system names 261 */ 262 @CheckReturnValue 263 @Nonnull 264 public List<Block> getBlocksOccupiedByRosterEntry(@Nonnull BasicRosterEntry re) { 265 List<Block> blockList = new ArrayList<>(); 266 getNamedBeanSet().stream().forEach(b -> { 267 if (b != null) { 268 Object obj = b.getValue(); 269 if ( obj != null && blockValueEqualsRosterEntry(obj, re)) { 270 blockList.add(b); 271 } 272 } 273 }); 274 return blockList; 275 } 276 277 private boolean blockValueEqualsRosterEntry( @Nonnull Object obj, @Nonnull BasicRosterEntry re ){ 278 return ( obj instanceof BasicRosterEntry && obj == re) || 279 obj.toString().equals(re.getId()) || 280 obj.toString().equals(re.getDccAddress()); 281 } 282 283 private Instant lastTimeLayoutPowerOn; // the most recent time any power manager had a power ON event 284 285 /** 286 * Listen for changes to the power state from any power managers 287 * in use in order to track how long it's been since power was applied 288 * to the layout. This information is used in {@link Block#goingActive()} 289 * when deciding whether to restore a block's last value. 290 * 291 * Also listen for additions/removals or PowerManagers 292 * 293 * @param e the change event 294 */ 295 @Override 296 public void propertyChange(PropertyChangeEvent e) { 297 super.propertyChange(e); 298 if ( PowerManager.POWER.equals(e.getPropertyName())) { 299 try { 300 PowerManager pm = (PowerManager) e.getSource(); 301 if (pm.getPower() == PowerManager.ON) { 302 lastTimeLayoutPowerOn = Instant.now(); 303 } 304 } catch (NoSuchMethodError xe) { 305 // do nothing 306 } 307 } 308 if (powerManagerChangeName.equals(e.getPropertyName())) { 309 if (e.getNewValue() == null) { 310 // powermanager has been removed 311 PowerManager pm = (PowerManager) e.getOldValue(); 312 pm.removePropertyChangeListener(this); 313 } else { 314 // a powermanager has been added 315 PowerManager pm = (PowerManager) e.getNewValue(); 316 pm.addPropertyChangeListener(this); 317 } 318 } 319 } 320 321 /** 322 * Get the amount of time since the layout was last powered up, 323 * in milliseconds. If the layout has not been powered up as far as 324 * JMRI knows it returns a very long time indeed. 325 * 326 * @return long int 327 */ 328 public long timeSinceLastLayoutPowerOn() { 329 if (lastTimeLayoutPowerOn == null) { 330 return Long.MAX_VALUE; 331 } 332 return Instant.now().toEpochMilli() - lastTimeLayoutPowerOn.toEpochMilli(); 333 } 334 335 @Override 336 @Nonnull 337 public Block provide(@Nonnull String name) { 338 return provideBlock(name); 339 } 340 341 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockManager.class); 342 343}