001package jmri.jmrit.simpleclock;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.beans.PropertyChangeListener;
006import java.time.Instant;
007import java.util.Calendar;
008import java.util.Date;
009
010import jmri.*;
011import jmri.jmrix.internal.InternalSystemConnectionMemo;
012
013/**
014 * Provide basic Timebase implementation from system clock.
015 * <p>
016 * This implementation provides for the internal clock and for one hardware
017 * clock. A number of hooks and comments are provided below for implementing
018 * multiple hardware clocks should that ever be done.
019 * <p>
020 * The setTimeValue member is the fast time when the clock started. The
021 * startAtTime member is the wall-clock time when the clock was started.
022 * Together, those can be used to calculate the current fast time.
023 * <p>
024 * The pauseTime member is used to indicate that the Timebase was paused. If
025 * non-null, it indicates the current fast time when the clock was paused.
026 *
027 * @author Bob Jacobsen Copyright (C) 2004, 2007 Dave Duchamp - 2007
028 *         additions/revisions for handling one hardware clock
029 */
030public class SimpleTimebase extends jmri.implementation.AbstractNamedBean implements Timebase {
031
032    public static final double MINIMUM_RATE = 0.1;
033    public static final double MAXIMUM_RATE = 100;
034
035    protected final SystemConnectionMemo memo;
036
037    public SimpleTimebase(InternalSystemConnectionMemo memo) {
038        super("SIMPLECLOCK");
039        this.memo = memo;
040        // initialize time-containing memory
041        try {
042            clockMemory = InstanceManager.memoryManagerInstance().provideMemory(memo.getSystemPrefix()+"MCURRENTTIME");
043            clockMemory.setValue("--");
044        } catch (IllegalArgumentException ex) {
045            log.warn("Unable to create CURRENTTIME time memory variable");
046        }
047
048        init();
049
050    }
051
052    final void init(){
053
054        // set to start counting from now
055        setTime(new Date());
056        pauseTime = null;
057        // initialize start/stop sensor for time running
058        try {
059            clockSensor = InstanceManager.sensorManagerInstance().provideSensor(memo.getSystemPrefix()+"SCLOCKRUNNING");
060            clockSensor.setKnownState(Sensor.ACTIVE);
061            clockSensor.addPropertyChangeListener(this::clockSensorChanged);
062        } catch (JmriException e) {
063            log.warn("Exception setting CLOCKRUNNING sensor ACTIVE", e);
064        }
065        // initialize rate factor-containing memory
066        if (InstanceManager.getNullableDefault(MemoryManager.class) != null) {
067            // only try to create memory if memories are supported
068            try {
069                factorMemory = InstanceManager.memoryManagerInstance()
070                    .provideMemory(memo.getSystemPrefix()+"MRATEFACTOR");
071                factorMemory.setValue(userGetRate());
072            } catch (IllegalArgumentException ex) {
073                log.warn("Unable to create RATEFACTOR time memory variable");
074            }
075        }
076
077    }
078
079    /**
080     * {@inheritDoc}
081     */
082    @Override
083    public String getBeanType() {
084        return Bundle.getMessage("BeanNameTime");
085    }
086
087    /**
088     * {@inheritDoc}
089     */
090    @Override
091    public Date getTime() {
092        // is clock stopped?
093        if (pauseTime != null) {
094            return new Date(pauseTime.getTime()); // to ensure not modified outside
095        } // clock running
096        long elapsedMSec = (new Date()).getTime() - startAtTime.getTime();
097        long nowMSec = setTimeValue.getTime() + (long) (mFactor * elapsedMSec);
098        return new Date(nowMSec);
099    }
100
101    /**
102     * {@inheritDoc}
103     */
104    @Override
105    public void setTime(Date d) {
106        startAtTime = new Date(); // set now in wall clock time
107        setTimeValue = new Date(d.getTime()); // to ensure not modified from outside
108        if ( synchronizeWithHardware && InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) {
109            // send new time to all hardware clocks, except the hardware time source if there is one
110            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
111            InstanceManager.getDefault(ClockControl.class).setTime(d);
112        }
113        if (pauseTime != null) {
114            pauseTime = setTimeValue; // if stopped, continue stopped at new time
115        }
116        handleAlarm(null);
117    }
118
119    /**
120     * {@inheritDoc}
121     */
122    @Override
123    public void setTime(Instant i) {
124        setTime(Date.from(i));
125    }
126
127    /**
128     * {@inheritDoc}
129     */
130    @Override
131    public void userSetTime(Date d) {
132        // this call only results from user changing fast clock time in Setup Fast Clock
133        startAtTime = new Date(); // set now in wall clock time
134        setTimeValue = new Date(d.getTime()); // to ensure not modified from outside
135        if (synchronizeWithHardware) {
136            // send new time to all hardware clocks, including the hardware time source if there is one
137            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
138            InstanceManager.getDefault(jmri.ClockControl.class).setTime(d);
139        } else if (!internalMaster && (hardwareTimeSource != null)) {
140            // if not synchronizing, send to the hardware time source if there is one
141            hardwareTimeSource.setTime(d);
142        }
143        if (pauseTime != null) {
144            pauseTime = setTimeValue; // if stopped, continue stopped at new time
145        }
146        handleAlarm(null);
147    }
148
149    /**
150     * {@inheritDoc}
151     */
152    @Override
153    public void setRun(boolean run) {
154        if (run && pauseTime != null) {
155            // starting of stopped clock
156            setTime(pauseTime);
157            if (synchronizeWithHardware) {
158                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
159                InstanceManager.getDefault(ClockControl.class).startHardwareClock(getTime());
160            } else if (!internalMaster && hardwareTimeSource != null) {
161                hardwareTimeSource.startHardwareClock(getTime());
162            }
163            pauseTime = null;
164            if (clockSensor != null) {
165                try {
166                    clockSensor.setKnownState(Sensor.ACTIVE);
167                } catch (JmriException e) {
168                    log.warn("Exception setting ISClockRunning sensor ACTIVE", e);
169                }
170            }
171        } else if (!run && pauseTime == null) {
172            // stopping of running clock:
173            // Store time it was stopped, and stop it
174            pauseTime = getTime();
175            if (synchronizeWithHardware) {
176                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
177                InstanceManager.getDefault(ClockControl.class).stopHardwareClock();
178            } else if (!internalMaster && hardwareTimeSource != null) {
179                hardwareTimeSource.stopHardwareClock();
180            }
181            if (clockSensor != null) {
182                try {
183                    clockSensor.setKnownState(Sensor.INACTIVE);
184                } catch (jmri.JmriException e) {
185                    log.warn("Exception setting ISClockRunning sensor INACTIVE", e);
186                }
187            }
188        }
189        firePropertyChange(PROPERTY_CHANGE_RUN, !run, run); // old, then new
190        handleAlarm(null);
191    }
192
193    /**
194     * {@inheritDoc}
195     */
196    @Override
197    public boolean getRun() {
198        return pauseTime == null;
199    }
200
201    /**
202     * {@inheritDoc}
203     */
204    @Override
205    public void setRate(double factor) throws TimebaseRateException {
206        checkRateValid(factor);
207        if (internalMaster && (!notInitialized)) {
208            log.error("Probable Error - questionable attempt to change fast clock rate");
209        }
210        double oldFactor = mFactor;
211        Date now = getTime();
212        // actually make the change
213        mFactor = factor;
214        if (internalMaster || notInitialized) {
215            hardwareFactor = factor;
216        }
217        if (internalMaster || (synchronizeWithHardware && notInitialized)) {
218            // send new rate to all hardware clocks, except the hardware time source if there is one
219            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
220            if (InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) {
221                InstanceManager.getDefault(ClockControl.class).setRate(factor);
222            }
223        }
224        // make sure time is right with new rate
225        setTime(now);
226        // notify listeners if internal master
227        if (internalMaster) {
228            firePropertyChange("rate", oldFactor, factor); // old, then new
229        }
230        handleAlarm(null);
231    }
232
233    /**
234     * {@inheritDoc}
235     */
236    @Override
237    public void userSetRate(double factor) throws TimebaseRateException {
238        // this call is used when user changes fast clock rate either in Setup Fast Clock or via a ClockControl
239        // implementation
240        checkRateValid(factor);
241        double oldFactor = hardwareFactor;
242        Date now = getTime();
243        // actually make the change
244        mFactor = factor;
245        hardwareFactor = factor;
246        if (synchronizeWithHardware) {
247            // send new rate to all hardware clocks, including the hardware time source if there is one
248            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
249            InstanceManager.getDefault(ClockControl.class).setRate(factor);
250        } else if (!internalMaster && (hardwareTimeSource != null)) {
251            // if not synchronizing, send to the hardware time source if there is one
252            hardwareTimeSource.setRate(factor);
253        }
254        // make sure time is right with new rate
255        setTime(now);
256        // update memory
257        updateMemory(factor);
258        // notify listeners
259        firePropertyChange("rate", oldFactor, factor); // old, then new
260        handleAlarm(null);
261    }
262
263    private void checkRateValid(double factor) throws TimebaseRateException {
264        if (factor < MINIMUM_RATE || factor > MAXIMUM_RATE) {
265            log.error("rate of {} is out of reasonable range {} - {}", factor, MINIMUM_RATE, MAXIMUM_RATE);
266            throw new TimebaseRateException(Bundle.getMessage("IncorrectRate", factor, MINIMUM_RATE, MAXIMUM_RATE));
267        }
268    }
269
270    /**
271     * {@inheritDoc}
272     */
273    @Override
274    public double getRate() {
275        return mFactor;
276    }
277
278    /**
279     * {@inheritDoc}
280     */
281    @Override
282    public double userGetRate() {
283        return ( internalMaster ? mFactor : hardwareFactor);
284    }
285
286    /**
287     * {@inheritDoc}
288     */
289    @Override
290    public void setInternalMaster(boolean master, boolean update) {
291        if (master != internalMaster) {
292            internalMaster = master;
293            if (internalMaster) {
294                mFactor = hardwareFactor; // get rid of any fiddled rate present
295            }
296            if (update) {
297                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
298                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(userGetRate(),
299                        getTime(), false);
300            }
301
302            if (internalMaster) {
303                masterName = "";
304                hardwareTimeSource = null;
305            } else {
306                // Note if there are multiple hardware clocks, this should be changed to correctly
307                // identify which hardware clock has been chosen-currently assumes only one
308                hardwareTimeSource = InstanceManager.getDefault(ClockControl.class);
309                masterName = hardwareTimeSource.getHardwareClockName();
310            }
311            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed            
312        }
313    }
314
315    /**
316     * {@inheritDoc}
317     */
318    @Override
319    public boolean getInternalMaster() {
320        return internalMaster;
321    }
322
323    /**
324     * {@inheritDoc}
325     */
326    @Override
327    public void setMasterName(String name) {
328        if (!internalMaster) {
329            masterName = name;
330            // if multiple clocks, this must be replaced by a loop over all hardware clocks to identify
331            // the one that is the hardware time source
332            hardwareTimeSource = InstanceManager.getDefault(ClockControl.class);
333        } else {
334            masterName = "";
335            hardwareTimeSource = null;
336        }
337    }
338
339    /**
340     * {@inheritDoc}
341     */
342    @Override
343    public String getMasterName() {
344        return masterName;
345    }
346
347    /**
348     * {@inheritDoc}
349     */
350    @Override
351    public void setSynchronize(boolean synchronize, boolean update) {
352        if (synchronizeWithHardware != synchronize) {
353            synchronizeWithHardware = synchronize;
354            if (update) {
355                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
356                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
357                   userGetRate(), getTime(), false);
358            }
359            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
360        }
361    }
362
363    /**
364     * {@inheritDoc}
365     */
366    @Override
367    public boolean getSynchronize() {
368        return synchronizeWithHardware;
369    }
370
371    /**
372     * If update true, calls initializeHardwareClock.
373     * {@inheritDoc}
374     */
375    @Override
376    public void setCorrectHardware(boolean correct, boolean update) {
377        if (correctHardware != correct) {
378            correctHardware = correct;
379            if (update) {
380                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
381                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
382                userGetRate(), getTime(), false);
383            }
384            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
385        }
386    }
387
388    /**
389     * {@inheritDoc}
390     */
391    @Override
392    public boolean getCorrectHardware() {
393        return correctHardware;
394    }
395
396    /**
397     * {@inheritDoc}
398     */
399    @Override
400    public void set12HourDisplay(boolean display, boolean update) {
401        if (display != display12HourClock) {
402            display12HourClock = display;
403            if (update) {
404                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
405                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
406                userGetRate(), getTime(), false);
407            }
408        }
409    }
410
411    /**
412     * {@inheritDoc}
413     */
414    @Override
415    public boolean use12HourDisplay() {
416        return display12HourClock;
417    }
418
419    /**
420     * {@inheritDoc}
421     */
422    @Override
423    public void setClockInitialRunState(ClockInitialRunState state) {
424        if (initialState != state) {
425            initialState = state;
426            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
427        }
428    }
429
430    /**
431     * {@inheritDoc}
432     */
433    @Override
434    public ClockInitialRunState getClockInitialRunState() {
435        return initialState;
436    }
437
438    /**
439     * {@inheritDoc}
440     */
441    @Override
442    public void setShowStopButton(boolean displayed) {
443        if (showStopButton != displayed) {
444            showStopButton = displayed;
445            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
446        }
447    }
448
449    /**
450     * {@inheritDoc}
451     */
452    @Override
453    public boolean getShowStopButton() {
454        return showStopButton;
455    }
456
457    /**
458     * {@inheritDoc}
459     */
460    @Override
461    public void setStartSetTime(boolean set, Date time) {
462        if (startSetTime!=set || startTime!=new Date(time.getTime())) {
463            startSetTime = set;
464            startTime = new Date(time.getTime());
465            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
466        }
467    }
468
469    /**
470     * {@inheritDoc}
471     */
472    @Override
473    public boolean getStartSetTime() {
474        return startSetTime;
475    }
476
477    /**
478     * {@inheritDoc}
479     */
480    @Override
481    public void setStartRate(double factor) {
482        if (Math.abs(startupFactor - factor) > 0.0001) { //avoid possible float precision errors
483            startupFactor = factor;
484            haveStartupFactor = true;
485            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
486        }
487    }
488
489    /**
490     * {@inheritDoc}
491     */
492    @Override
493    public double getStartRate() {
494        if (haveStartupFactor) {
495            return startupFactor;
496        } else {
497            return userGetRate();
498        }
499    }
500
501    /**
502     * {@inheritDoc}
503     */
504    @Override
505    public void setSetRateAtStart(boolean set) {
506        if (startSetRate != set) {
507            startSetRate = set;
508            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
509        }
510    }
511
512    /**
513     * {@inheritDoc}
514     */
515    @Override
516    public boolean getSetRateAtStart() {
517        return startSetRate;
518    }
519
520    /**
521     * {@inheritDoc}
522     */
523    @Override
524    public Date getStartTime() {
525        return new Date(startTime.getTime());
526    }
527
528    /**
529     * {@inheritDoc}
530     */
531    @Override
532    public void setStartClockOption(int option) {
533        if (startClockOption != option) {
534            startClockOption = option;
535            firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed
536        }
537    }
538
539    /**
540     * {@inheritDoc}
541     */
542    @Override
543    public int getStartClockOption() {
544        return startClockOption;
545    }
546
547    /**
548     * The following method should only be invoked at start up.
549     * {@inheritDoc}
550     */
551    @Override
552    public void initializeClock() {
553        switch (startClockOption) {
554            case NIXIE_CLOCK:
555                jmri.jmrit.nixieclock.NixieClockFrame f = new jmri.jmrit.nixieclock.NixieClockFrame();
556                f.setVisible(true);
557                break;
558            case ANALOG_CLOCK:
559                jmri.jmrit.analogclock.AnalogClockFrame g = new jmri.jmrit.analogclock.AnalogClockFrame();
560                g.setVisible(true);
561                break;
562            case LCD_CLOCK:
563                jmri.jmrit.lcdclock.LcdClockFrame h = new jmri.jmrit.lcdclock.LcdClockFrame();
564                h.setVisible(true);
565                break;
566            case PRAGOTRON_CLOCK:
567                jmri.jmrit.pragotronclock.PragotronClockFrame p = new jmri.jmrit.pragotronclock.PragotronClockFrame();
568                p.setVisible(true);
569                break;
570            default:
571                log.debug("initializeClock() called with invalid startClockOption: {}", startClockOption);
572        }
573    }
574
575    /**
576     * {@inheritDoc}
577     */
578    @Override
579    public void initializeHardwareClock() {
580        boolean startStopped = (initialState == ClockInitialRunState.DO_STOP);
581        if (synchronizeWithHardware || correctHardware) {
582            if (startStopped) {
583                InstanceManager.getList(ClockControl.class).forEach( cc -> 
584                    cc.initializeHardwareClock( 0, getTime(), (!internalMaster && !startSetTime)) );
585            } else {
586                InstanceManager.getList(ClockControl.class).forEach( cc -> 
587                    cc.initializeHardwareClock( mFactor, getTime(), (!internalMaster && !startSetTime)) );
588            }
589        } else if (!internalMaster) {
590            if (startStopped) {
591                hardwareTimeSource.initializeHardwareClock(0, getTime(), (!startSetTime));
592            } else {
593                hardwareTimeSource.initializeHardwareClock(hardwareFactor, getTime(), (!startSetTime));
594            }
595        }
596        notInitialized = false;
597    }
598
599    /**
600     * {@inheritDoc}
601     */
602    @Override
603    public boolean getIsInitialized() {
604        return (!notInitialized);
605    }
606
607    /**
608     * Handle a change in the clock running sensor
609     */
610    private void clockSensorChanged(java.beans.PropertyChangeEvent e) {
611        if (clockSensor.getKnownState() == Sensor.ACTIVE) {
612            // simply return if clock is already running
613            if (pauseTime == null) {
614                return;
615            }
616            setRun(true);
617        } else {
618            // simply return if clock is already stopped
619            if (pauseTime != null) {
620                return;
621            }
622            setRun(false);
623        }
624    }
625
626    /**
627     * Stops Timer.
628     * {@inheritDoc}
629     */
630    @Override
631    public void dispose() {
632        if (timer != null) {
633            // end this timer
634            timer.setRepeats(false); // just in case
635            timer.stop();
636
637            ActionListener[] listeners = timer.getListeners(ActionListener.class);
638            for (ActionListener listener : listeners) {
639                timer.removeActionListener(listener);
640            }
641            timer = null;
642        }
643        if ( clockSensor != null ) {
644            clockSensor.removePropertyChangeListener(this::clockSensorChanged);
645        }
646        super.dispose(); // remove standard property change listeners
647    }
648
649    /**
650     * InstanceManager.getDefault(jmri.Timebase.class) variables and options
651     */
652    private double mFactor = 1.0; // this is the rate factor for the JMRI fast clock
653    private double hardwareFactor = 1.0; // this is the rate factor for the hardware clock
654    //  The above is necessary to support hardware clock Time Sources that fiddle with mFactor to
655    //      synchronize, instead of sending over a new time to synchronize.
656    private double startupFactor = 1.0; // this is the rate requested at startup
657    private boolean startSetRate = true; // if true, the hardware rate will be set to
658    private boolean haveStartupFactor = false; // true if startup factor was ever set.
659    // startupFactor at startup.
660
661    private Date startAtTime;
662    private Date setTimeValue;
663    private Date pauseTime; // null value indicates clock is running
664    private Sensor clockSensor = null; // active when clock is running, inactive when stopped
665    private Memory clockMemory = null; // contains current time on each tick
666    private Memory factorMemory = null; // contains the rate factor for the fast clock
667
668    private boolean internalMaster = true; // false indicates a hardware clock is the master
669    private String masterName = ""; // name of hardware time source, if not internal master
670    private ClockControl hardwareTimeSource = null; // ClockControl instance of hardware time source
671    private boolean synchronizeWithHardware = false; // true indicates need to synchronize
672    private boolean correctHardware = false; // true indicates hardware correction requested
673    private boolean display12HourClock = false; // true if 12-hour clock display is requested
674    private ClockInitialRunState initialState = ClockInitialRunState.DO_START; // what to do with the clock running state at startup
675    private boolean startSetTime = false; // true indicates set fast clock to specified time at
676    //start up requested
677    private Date startTime = new Date(); // specified time for setting fast clock at start up
678    private int startClockOption = NONE; // request start of a clock at start up
679    private boolean notInitialized = true; // true before initialization received from start up
680    private boolean showStopButton = false; // true indicates start up with start/stop button displayed
681
682    private java.text.SimpleDateFormat timeStorageFormat = null;
683
684    private javax.swing.Timer timer = null;
685
686    /**
687     * Start the minute alarm ticking, if it isnt already.
688     */
689    void startAlarm() {
690        if (timer == null) {
691            handleAlarm(null);
692        }
693    }
694
695    private int oldHours = -1;
696    private int oldMinutes = -1;
697    private Date oldDate = null;
698
699    /**
700     * Handle an "alarm", which is used to count off minutes.
701     * <p>
702     * Listeners will be notified if the hours or minutes changed
703     * since the last time.
704     * @param e Event which triggered this
705     */
706    void handleAlarm(ActionEvent e) {
707        // on first pass, set up the timer to call this routine
708        if (timer == null) {
709            timer = new javax.swing.Timer(60 * 1000, this::handleAlarm);
710        }
711
712        Calendar calendar = Calendar.getInstance();
713        timer.stop();
714        Date date = getTime();
715        calendar.setTime(date);
716        int waitSeconds = 60 - calendar.get(Calendar.SECOND);
717        int delay = (int) (waitSeconds * 1000 / mFactor) + 100; // make sure you miss the time transition
718        timer.setInitialDelay(delay);
719        timer.setRepeats(true); // in case we run by
720        timer.start();
721
722        // and notify the others
723        calendar.setTime(date);
724        int hours = calendar.get(Calendar.HOUR_OF_DAY);
725        int minutes = calendar.get(Calendar.MINUTE);
726        if (hours != oldHours || minutes != oldMinutes) {
727            // update memory
728            updateMemory(date);
729            // notify listeners
730            firePropertyChange(PROPERTY_CHANGE_MINUTES, Double.valueOf(oldMinutes), Double.valueOf(minutes));
731            firePropertyChange(PROPERTY_CHANGE_TIME, oldDate != null ? new Date(oldDate.getTime()) : null,
732                new Date(date.getTime())); // to ensure not modified outside
733        }
734        oldDate = date;
735        oldHours = hours;
736        oldMinutes = minutes;
737    }
738
739    void updateMemory(Date date) {
740        if (timeStorageFormat == null) {
741            String pattern = java.util.ResourceBundle.getBundle("jmri.jmrit.simpleclock.SimpleClockBundle")
742                .getString("TimeStorageFormat");
743            try {
744                timeStorageFormat = new java.text.SimpleDateFormat(pattern);
745            } catch (IllegalArgumentException e) {
746                log.info("Unable to parse date / time format: {}",pattern);
747                log.info("For supported formats see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/SimpleDateFormat.html");
748                log.info("Dropping back to default time format (h:mm a) 4:56 PM, due to exception", e);
749                timeStorageFormat = new java.text.SimpleDateFormat("h:mm a");
750            }
751        }
752        clockMemory.setValue(timeStorageFormat.format(date));
753    }
754
755    void updateMemory(double factor) {
756        factorMemory.setValue(factor);
757    }
758
759    /**
760     * {@inheritDoc}
761     */
762    @Override
763    public void addMinuteChangeListener(PropertyChangeListener l) {
764        addPropertyChangeListener(PROPERTY_CHANGE_MINUTES, l);
765    }
766
767    /**
768     * {@inheritDoc}
769     */
770    @Override
771    public void removeMinuteChangeListener(PropertyChangeListener l) {
772        removePropertyChangeListener(PROPERTY_CHANGE_MINUTES, l);
773    }
774
775    /**
776     * {@inheritDoc}
777     */
778    @Override
779    public PropertyChangeListener[] getMinuteChangeListeners() {
780        return getPropertyChangeListeners(PROPERTY_CHANGE_MINUTES);
781    }
782
783    @Override
784    public void addPropertyChangeListener(PropertyChangeListener listener) {
785        super.addPropertyChangeListener(listener);
786        startAlarm();
787    }
788
789
790    @Override
791    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
792        super.addPropertyChangeListener(propertyName, listener);
793        if (propertyName != null && (propertyName.equals(PROPERTY_CHANGE_MINUTES) || propertyName.equals(PROPERTY_CHANGE_TIME))) {
794            startAlarm();
795        }
796    }
797
798    /**
799     * Implementation does nothing.
800     * {@inheritDoc}
801     */
802    @Override
803    public void setState(int s) throws jmri.JmriException {
804    }
805
806    /**
807     * Implementation returns 0 .
808     * {@inheritDoc}
809     */
810    @Override
811    public int getState() {
812        return 0;
813    }
814
815    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SimpleTimebase.class);
816
817}