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