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}