001package jmri.managers;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.List;
006import java.util.Objects;
007import java.util.Set;
008import javax.annotation.CheckForNull;
009import javax.annotation.Nonnull;
010import jmri.*;
011import jmri.implementation.AbstractInstanceInitializer;
012import jmri.implementation.DefaultIdTag;
013import jmri.SystemConnectionMemo;
014import jmri.jmrix.internal.InternalSystemConnectionMemo;
015import jmri.managers.configurexml.DefaultIdTagManagerXml;
016import org.openide.util.lookup.ServiceProvider;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
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 DefaultIdTagManager(SystemConnectionMemo memo) {
037        super(memo);
038    }
039
040    /** {@inheritDoc} */
041    @Override
042    public int getXMLOrder() {
043        return Manager.IDTAGS;
044    }
045
046    /** {@inheritDoc} */
047    @Override
048    public boolean isInitialised() {
049        return initialised;
050    }
051
052    /** {@inheritDoc} */
053    @Override
054    public void init() {
055        log.debug("init called");
056        if (!initialised && !loading) {
057            log.debug("Initialising");
058            // Load when created
059            loading = true;
060            readIdTagDetails();
061            loading = false;
062            dirty = false;
063            initShutdownTask();
064            initialised = 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    @Nonnull
110    public IdTag provideIdTag(@Nonnull String name) throws IllegalArgumentException {
111        if (!initialised && !loading) {
112            init();
113        }
114        IdTag t = getIdTag(name);
115        if (t != null) {
116            return t;
117        }
118        if (name.startsWith(getSystemPrefix() + typeLetter())) {
119            return newIdTag(name, null);
120        } else if (!name.isEmpty()) {
121            return newIdTag(makeSystemName(name), null);
122        } else {
123            throw new IllegalArgumentException("\"" + name + "\" is invalid");
124        }
125    }
126
127    /** {@inheritDoc} */
128    @CheckForNull
129    @Override
130    public IdTag getIdTag(@Nonnull String name) {
131        if (!initialised && !loading) {
132            init();
133        }
134
135        IdTag t = getBySystemName(makeSystemName(name));
136        if (t != null) {
137            return t;
138        }
139
140        t = getByUserName(name);
141        if (t != null) {
142            return t;
143        }
144
145        return getBySystemName(name);
146    }
147
148    /** {@inheritDoc} */
149    @CheckForNull
150    @Override
151    public IdTag getBySystemName(@Nonnull String name) {
152        if (!initialised && !loading) {
153            init();
154        }
155        return _tsys.get(name);
156    }
157
158    /** {@inheritDoc} */
159    @CheckForNull
160    @Override
161    public IdTag getByUserName(@Nonnull String key) {
162        if (!initialised && !loading) {
163            init();
164        }
165        return _tuser.get(key);
166    }
167
168    /** {@inheritDoc} */
169    @CheckForNull
170    @Override
171    public IdTag getByTagID(@Nonnull String tagID) {
172        if (!initialised && !loading) {
173            init();
174        }
175        return getBySystemName(makeSystemName(tagID));
176    }
177
178    @Nonnull
179    protected IdTag createNewIdTag(String systemName, String userName) throws IllegalArgumentException {
180        // Names start with the system prefix followed by D.
181        // Add the prefix if not present.
182        if (!systemName.startsWith(getSystemPrefix() + typeLetter())) {
183            systemName = getSystemPrefix() + typeLetter() + systemName;
184        }
185        return new DefaultIdTag(systemName, userName);
186    }
187
188    /**
189     * Provide ID Tag by UserName then SystemName, creates new IdTag if not found.
190     * {@inheritDoc} */
191    @Override
192    @Nonnull
193    public IdTag newIdTag(@Nonnull String systemName, @CheckForNull String userName) throws IllegalArgumentException {
194        if (!initialised && !loading) {
195            init();
196        }
197        log.debug("new IdTag:{};{}", systemName,userName); // NOI18N
198        Objects.requireNonNull(systemName, "SystemName cannot be null.");
199
200        // return existing if there is one
201        IdTag s;
202        if (userName != null)  {
203            s = getByUserName(userName);
204            if (s != null) {
205                if (getBySystemName(systemName) != s) {
206                    log.error("inconsistent user ({}) and system name ({}) results; userName related to ({})", userName, systemName, s.getSystemName());
207                }
208                return s;
209            }
210        }
211        s = getBySystemName(systemName);
212        if (s != null) {
213            if ((s.getUserName() == null) && (userName != null)) {
214                s.setUserName(userName);
215            } else if (userName != null) {
216                log.warn("Found IdTag via system name ({}) with non-null user name ({})", systemName, userName); // NOI18N
217            }
218            return s;
219        }
220
221        // doesn't exist, make a new one
222        s = createNewIdTag(systemName, userName);
223
224        // save in the maps
225        register(s);
226
227        return s;
228    }
229
230    /** {@inheritDoc} */
231    @Override
232    public void register(@Nonnull IdTag s) {
233        super.register(s);
234        this.setDirty(true);
235    }
236
237    /** {@inheritDoc} */
238    @Override
239    public void deregister(@Nonnull IdTag s) {
240        super.deregister(s);
241        this.setDirty(true);
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public void propertyChange(java.beans.PropertyChangeEvent e) {
247        super.propertyChange(e);
248        this.setDirty(true);
249    }
250
251    public void writeIdTagDetails() throws java.io.IOException {
252        if (this.dirty) {
253            new DefaultIdTagManagerXml(this,"IdTags.xml").store();  // NOI18N
254            this.dirty = false;
255            log.debug("...done writing IdTag details");
256        }
257    }
258
259    public void readIdTagDetails() {
260        log.debug("reading idTag Details");
261        new DefaultIdTagManagerXml(this,"IdTags.xml").load();  // NOI18N
262        this.dirty = false;
263        log.debug("...done reading IdTag details");
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public void setStateStored(boolean state) {
269        if (!initialised && !loading) {
270            init();
271        }
272        if (state != storeState) {
273            this.setDirty(true);
274        }
275        boolean old = storeState;
276        storeState = state;
277        firePropertyChange("StateStored", old, state);
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public boolean isStateStored() {
283        if (!initialised && !loading) {
284            init();
285        }
286        return storeState;
287    }
288
289    /** {@inheritDoc} */
290    @Override
291    public void setFastClockUsed(boolean fastClock) {
292        if (!initialised && !loading) {
293            init();
294        }
295        if (fastClock != useFastClock) {
296            this.setDirty(true);
297        }
298        boolean old = useFastClock;
299        useFastClock  = fastClock;
300        firePropertyChange("UseFastClock", old, fastClock);
301    }
302
303    /** {@inheritDoc} */
304    @Override
305    public boolean isFastClockUsed() {
306        if (!initialised && !loading) {
307            init();
308        }
309        return useFastClock;
310    }
311
312    /** {@inheritDoc} */
313    @Override
314    @Nonnull
315    public List<IdTag> getTagsForReporter(@Nonnull Reporter reporter, long threshold) {
316        List<IdTag> out = new ArrayList<>();
317        Date lastWhenLastSeen = new Date(0);
318
319        // First create a list of all tags seen by specified reporter
320        // and record the time most recently seen
321        for (IdTag n : _tsys.values()) {
322            IdTag t = n;
323            if (t.getWhereLastSeen() == reporter) {
324                out.add(t);
325                Date tagLastSeen = t.getWhenLastSeen();
326                if (tagLastSeen != null && tagLastSeen.after(lastWhenLastSeen)) {
327                    lastWhenLastSeen = tagLastSeen;
328                }
329            }
330        }
331
332        // Calculate the threshold time based on the most recently seen tag
333        Date thresholdTime = new Date(lastWhenLastSeen.getTime() - threshold);
334
335        // Now remove from the list all tags seen prior to the threshold time
336        out.removeIf(t -> {
337            Date tagLastSeen = t.getWhenLastSeen();
338            return tagLastSeen == null || tagLastSeen.before(thresholdTime);
339        });
340
341        return out;
342    }
343
344    private void setDirty(boolean dirty) {
345        this.dirty = dirty;
346    }
347
348    /** {@inheritDoc} */
349    @Override
350    public void dispose() {
351        if(this.shutDownTask!=null) {
352            InstanceManager.getDefault(ShutDownManager.class).deregister(this.shutDownTask);
353        }
354        super.dispose();
355    }
356
357    /** {@inheritDoc} */
358    @Override
359    @Nonnull
360    public String getBeanTypeHandled(boolean plural) {
361        return Bundle.getMessage(plural ? "BeanNameReporters" : "BeanNameReporter");
362    }
363
364    /**
365     * {@inheritDoc}
366     */
367    @Override
368    public Class<IdTag> getNamedBeanClass() {
369        return IdTag.class;
370    }
371
372    private static final Logger log = LoggerFactory.getLogger(DefaultIdTagManager.class);
373
374    @ServiceProvider(service = InstanceInitializer.class)
375    public static class Initializer extends AbstractInstanceInitializer {
376
377        @Override
378        @Nonnull
379        public <T> Object getDefault(Class<T> type) {
380            if (type.equals(IdTagManager.class)) {
381                return new DefaultIdTagManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
382            }
383            return super.getDefault(type);
384        }
385
386        @Override
387        @Nonnull
388        public Set<Class<?>> getInitalizes() {
389            Set<Class<?>> set = super.getInitalizes();
390            set.add(IdTagManager.class);
391            return set;
392        }
393    }
394
395}