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