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