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