001package jmri.managers; 002 003import java.util.*; 004 005import javax.annotation.CheckForNull; 006import javax.annotation.CheckReturnValue; 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.implementation.AbstractInstanceInitializer; 011import jmri.implementation.DefaultIdTag; 012import jmri.SystemConnectionMemo; 013import jmri.jmrix.internal.InternalSystemConnectionMemo; 014import jmri.managers.configurexml.DefaultIdTagManagerXml; 015import org.openide.util.lookup.ServiceProvider; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Concrete implementation for the Internal {@link jmri.IdTagManager} interface. 021 * 022 * @author Bob Jacobsen Copyright (C) 2010 023 * @author Matthew Harris Copyright (C) 2011 024 * @since 2.11.4 025 */ 026public class DefaultIdTagManager extends AbstractManager<IdTag> implements IdTagManager, Disposable { 027 028 protected boolean dirty = false; 029 private boolean initialised = false; 030 private boolean loading = false; 031 private boolean storeState = false; 032 private boolean useFastClock = false; 033 private Runnable shutDownTask = null; 034 035 public final static String PROPERTY_INITIALISED = "initialised"; 036 037 public DefaultIdTagManager(SystemConnectionMemo memo) { 038 super(memo); 039 } 040 041 /** {@inheritDoc} */ 042 @Override 043 public int getXMLOrder() { 044 return Manager.IDTAGS; 045 } 046 047 /** {@inheritDoc} */ 048 @Override 049 public boolean isInitialised() { 050 return initialised; 051 } 052 053 /** {@inheritDoc} */ 054 @Override 055 public void init() { 056 log.debug("init called"); 057 if (!initialised && !loading) { 058 log.debug("Initialising"); 059 // Load when created 060 loading = true; 061 readIdTagDetails(); 062 loading = false; 063 dirty = false; 064 initShutdownTask(); 065 initialised = true; 066 propertyChangeSupport.firePropertyChange(PROPERTY_INITIALISED, false, true); 067 } 068 } 069 070 protected void initShutdownTask(){ 071 // Create shutdown task to save 072 log.debug("Register ShutDown task"); 073 if (this.shutDownTask == null) { 074 this.shutDownTask = () -> { 075 // Save IdTag details prior to exit, if necessary 076 log.debug("Start writing IdTag details..."); 077 try { 078 writeIdTagDetails(); 079 } catch (java.io.IOException ioe) { 080 log.error("Exception writing IdTags", ioe); 081 } 082 }; 083 InstanceManager.getDefault(ShutDownManager.class).register(this.shutDownTask); 084 } 085 } 086 087 /** 088 * {@inheritDoc} 089 * Don't want to store this information 090 */ 091 @Override 092 protected void registerSelf() { 093 // override to do nothing 094 } 095 096 /** {@inheritDoc} */ 097 @Override 098 public char typeLetter() { 099 return 'D'; 100 } 101 102 /** {@inheritDoc} */ 103 @Override 104 @Nonnull 105 public IdTag provide(@Nonnull String name) throws IllegalArgumentException { 106 return provideIdTag(name); 107 } 108 109 /** {@inheritDoc} */ 110 @Override 111 @CheckReturnValue 112 @Nonnull 113 public SortedSet<IdTag> getNamedBeanSet() { 114 // need to ensure that load has taken place before returning 115 if (!initialised && !loading) { 116 init(); 117 } 118 return super.getNamedBeanSet(); 119 } 120 121 /** {@inheritDoc} */ 122 @Override 123 @CheckReturnValue 124 public int getObjectCount() { 125 // need to ensure that load has taken place before returning 126 if (!initialised && !loading) { 127 init(); 128 } 129 return super.getObjectCount(); 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 @Nonnull 135 public IdTag provideIdTag(@Nonnull String name) throws IllegalArgumentException { 136 if (!initialised && !loading) { 137 init(); 138 } 139 IdTag t = getIdTag(name); 140 if (t != null) { 141 return t; 142 } 143 if (name.startsWith(getSystemPrefix() + typeLetter())) { 144 return newIdTag(name, null); 145 } else if (!name.isEmpty()) { 146 return newIdTag(makeSystemName(name), null); 147 } else { 148 throw new IllegalArgumentException("\"" + name + "\" is invalid"); 149 } 150 } 151 152 /** {@inheritDoc} */ 153 @CheckForNull 154 @Override 155 public IdTag getIdTag(@Nonnull String name) { 156 if (!initialised && !loading) { 157 init(); 158 } 159 160 IdTag t = getBySystemName(makeSystemName(name)); 161 if (t != null) { 162 return t; 163 } 164 165 t = getByUserName(name); 166 if (t != null) { 167 return t; 168 } 169 170 return getBySystemName(name); 171 } 172 173 /** {@inheritDoc} */ 174 @CheckForNull 175 @Override 176 public IdTag getBySystemName(@Nonnull String name) { 177 if (!initialised && !loading) { 178 init(); 179 } 180 return _tsys.get(name); 181 } 182 183 /** {@inheritDoc} */ 184 @CheckForNull 185 @Override 186 public IdTag getByUserName(@Nonnull String key) { 187 if (!initialised && !loading) { 188 init(); 189 } 190 return _tuser.get(key); 191 } 192 193 /** {@inheritDoc} */ 194 @CheckForNull 195 @Override 196 public IdTag getByTagID(@Nonnull String tagID) { 197 if (!initialised && !loading) { 198 init(); 199 } 200 return getBySystemName(makeSystemName(tagID)); 201 } 202 203 @Nonnull 204 protected IdTag createNewIdTag(String systemName, String userName) throws IllegalArgumentException { 205 // Names start with the system prefix followed by D. 206 // Add the prefix if not present. 207 if (!systemName.startsWith(getSystemPrefix() + typeLetter())) { 208 systemName = getSystemPrefix() + typeLetter() + systemName; 209 } 210 return new DefaultIdTag(systemName, userName); 211 } 212 213 /** 214 * Provide ID Tag by UserName then SystemName, creates new IdTag if not found. 215 * {@inheritDoc} */ 216 @Override 217 @Nonnull 218 public IdTag newIdTag(@Nonnull String systemName, @CheckForNull String userName) throws IllegalArgumentException { 219 if (!initialised && !loading) { 220 init(); 221 } 222 log.debug("new IdTag:{};{}", systemName,userName); // NOI18N 223 Objects.requireNonNull(systemName, "SystemName cannot be null."); 224 225 // return existing if there is one 226 IdTag s; 227 if (userName != null) { 228 s = getByUserName(userName); 229 if (s != null) { 230 if (getBySystemName(systemName) != s) { 231 log.error("inconsistent user ({}) and system name ({}) results; userName related to ({})", userName, systemName, s.getSystemName()); 232 } 233 return s; 234 } 235 } 236 s = getBySystemName(systemName); 237 if (s != null) { 238 if ((s.getUserName() == null) && (userName != null)) { 239 s.setUserName(userName); 240 } else if (userName != null) { 241 log.warn("Found IdTag via system name ({}) with non-null user name ({})", systemName, userName); // NOI18N 242 } 243 return s; 244 } 245 246 // doesn't exist, make a new one 247 s = createNewIdTag(systemName, userName); 248 249 // save in the maps 250 register(s); 251 252 return s; 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public void register(@Nonnull IdTag s) { 258 super.register(s); 259 this.setDirty(true); 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public void deregister(@Nonnull IdTag s) { 265 super.deregister(s); 266 this.setDirty(true); 267 } 268 269 /** {@inheritDoc} */ 270 @Override 271 public void propertyChange(java.beans.PropertyChangeEvent e) { 272 super.propertyChange(e); 273 this.setDirty(true); 274 } 275 276 public void writeIdTagDetails() throws java.io.IOException { 277 if (this.dirty) { 278 new DefaultIdTagManagerXml(this,"IdTags.xml").store(); // NOI18N 279 this.dirty = false; 280 log.debug("...done writing IdTag details"); 281 } 282 } 283 284 public void readIdTagDetails() { 285 log.debug("reading idTag Details"); 286 new DefaultIdTagManagerXml(this,"IdTags.xml").load(); // NOI18N 287 this.dirty = false; 288 log.debug("...done reading IdTag details"); 289 } 290 291 /** {@inheritDoc} */ 292 @Override 293 public void setStateStored(boolean state) { 294 if (!initialised && !loading) { 295 init(); 296 } 297 if (state != storeState) { 298 this.setDirty(true); 299 } 300 boolean old = storeState; 301 storeState = state; 302 firePropertyChange("StateStored", old, state); 303 } 304 305 /** {@inheritDoc} */ 306 @Override 307 public boolean isStateStored() { 308 if (!initialised && !loading) { 309 init(); 310 } 311 return storeState; 312 } 313 314 /** {@inheritDoc} */ 315 @Override 316 public void setFastClockUsed(boolean fastClock) { 317 if (!initialised && !loading) { 318 init(); 319 } 320 if (fastClock != useFastClock) { 321 this.setDirty(true); 322 } 323 boolean old = useFastClock; 324 useFastClock = fastClock; 325 firePropertyChange("UseFastClock", old, fastClock); 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public boolean isFastClockUsed() { 331 if (!initialised && !loading) { 332 init(); 333 } 334 return useFastClock; 335 } 336 337 /** {@inheritDoc} */ 338 @Override 339 @Nonnull 340 public List<IdTag> getTagsForReporter(@Nonnull Reporter reporter, long threshold) { 341 List<IdTag> out = new ArrayList<>(); 342 Date lastWhenLastSeen = new Date(0); 343 344 // First create a list of all tags seen by specified reporter 345 // and record the time most recently seen 346 for (IdTag n : _tsys.values()) { 347 IdTag t = n; 348 if (t.getWhereLastSeen() == reporter) { 349 out.add(t); 350 Date tagLastSeen = t.getWhenLastSeen(); 351 if (tagLastSeen != null && tagLastSeen.after(lastWhenLastSeen)) { 352 lastWhenLastSeen = tagLastSeen; 353 } 354 } 355 } 356 357 // Calculate the threshold time based on the most recently seen tag 358 Date thresholdTime = new Date(lastWhenLastSeen.getTime() - threshold); 359 360 // Now remove from the list all tags seen prior to the threshold time 361 out.removeIf(t -> { 362 Date tagLastSeen = t.getWhenLastSeen(); 363 return tagLastSeen == null || tagLastSeen.before(thresholdTime); 364 }); 365 366 return out; 367 } 368 369 private void setDirty(boolean dirty) { 370 this.dirty = dirty; 371 } 372 373 /** {@inheritDoc} */ 374 @Override 375 public void dispose() { 376 if(this.shutDownTask!=null) { 377 InstanceManager.getDefault(ShutDownManager.class).deregister(this.shutDownTask); 378 } 379 super.dispose(); 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 @Nonnull 385 public String getBeanTypeHandled(boolean plural) { 386 return Bundle.getMessage(plural ? "BeanNameIdTags" : "BeanNameIdTag"); 387 } 388 389 /** 390 * {@inheritDoc} 391 */ 392 @Override 393 public Class<IdTag> getNamedBeanClass() { 394 return IdTag.class; 395 } 396 397 private static final Logger log = LoggerFactory.getLogger(DefaultIdTagManager.class); 398 399 @ServiceProvider(service = InstanceInitializer.class) 400 public static class Initializer extends AbstractInstanceInitializer { 401 402 @Override 403 @Nonnull 404 public <T> Object getDefault(Class<T> type) { 405 if (type.equals(IdTagManager.class)) { 406 return new DefaultIdTagManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)); 407 } 408 return super.getDefault(type); 409 } 410 411 @Override 412 @Nonnull 413 public Set<Class<?>> getInitalizes() { 414 Set<Class<?>> set = super.getInitalizes(); 415 set.add(IdTagManager.class); 416 return set; 417 } 418 } 419 420}