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}