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}