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