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