001 package sale; 002 003 import java.util.*; 004 005 import java.io.*; 006 007 import javax.swing.*; 008 import java.awt.Rectangle; 009 010 import java.awt.event.*; 011 012 import users.*; 013 import log.*; 014 015 import sale.multiwindow.*; 016 import sale.events.*; 017 018 import data.NameContext; 019 import data.NameContextException; 020 021 import data.DataBasket; 022 import data.Stock; 023 import data.Catalog; 024 import data.DuplicateKeyException; 025 026 import resource.util.ResourceManager; 027 028 import util.*; 029 030 /** 031 * The central class in a SalesPoint application, responsible for central 032 * management tasks and for persistence. 033 * 034 * <p>There is only one instance of the Shop class per application, and you can 035 * obtain, or change this central, singleton instance through calls to 036 * {@link #getTheShop} or {@link #setTheShop}, resp.</p> 037 * 038 * <p>The Shop will manage the application's display, creating and removing 039 * additional SalesPoints' displays as necessary. Also, the Shop will offer a 040 * central MenuSheet, from which the user can select certain central, 041 * administrative actions, like shutdown, loadup, creating and removing 042 * SalesPoints, etc. This MenuSheet can, of course, be adapted. See 043 * {@link #createShopMenuSheet}, if you're interested in this.</p> 044 * 045 * <p>The Shop can make persistent the entire current state of the application 046 * by calling just one method: {@link #makePersistent}.</p> 047 * 048 * <p>The Shop serves as a {@link ProcessContext} for remote and background 049 * {@link SaleProcess processes}, which will be equipped with a 050 * {@link NullDisplay}. To find out about running processes at the Shop, see 051 * {@link #runProcess} and {@link #runBackgroundProcess}.</p> 052 * 053 * @author Steffen Zschaler 054 * @version 2.0 28/05/1999 055 * @since v2.0 056 */ 057 public class Shop extends Object implements SerializableListener { 058 059 /** 060 * The SalesPoints that belong to the system. 061 * 062 * @serial 063 */ 064 protected List m_lspSalesPoints = new LinkedList(); 065 066 /** 067 * The monitor synchronizing access to the list of SalesPoints. 068 */ 069 private transient Object m_oSalesPointsLock; 070 071 /** 072 * Return the monitor synchronizing access to the list of SalesPoints. 073 * 074 * @override Never 075 */ 076 protected final Object getSalesPointsLock() { 077 if (m_oSalesPointsLock == null) { 078 m_oSalesPointsLock = new Object(); 079 } 080 081 return m_oSalesPointsLock; 082 } 083 084 /** 085 * The current SalesPoint. 086 * 087 * @serial 088 */ 089 private SalesPoint m_spCurrent = null; 090 091 /** 092 * Flag indicating whether calls to {@link #setCurrentSalesPoint} are to have an effect or not. Used for 093 * optimization reasons. 094 * 095 * @serial 096 */ 097 private int m_nCurrentSalesPointIsAdjusting = 0; 098 099 /** 100 * The ShopFrames bounds. 101 * 102 * @serial 103 */ 104 protected Rectangle m_rShopFrameBounds = null; 105 106 /** 107 * A ProcessContext for one remote or background process. 108 */ 109 protected static class ProcessHandle implements ProcessContext { 110 111 /** 112 * The process for which this is the context. 113 * 114 * @serial 115 */ 116 protected SaleProcess m_p; 117 118 /** 119 * The display to be used. Defaults to {@link NullDisplay#s_ndGlobal}. 120 * 121 * @serial 122 */ 123 protected Display m_d = NullDisplay.s_ndGlobal; 124 125 /** 126 * The user to be used as the current user for the process. 127 * 128 * @serial 129 */ 130 protected User m_usr; 131 132 /** 133 * The DataBasket to be used. 134 * 135 * @serial 136 */ 137 protected DataBasket m_db; 138 139 /** 140 * Create a new ProcessHandle. 141 */ 142 public ProcessHandle(SaleProcess p, Display d, User usr, DataBasket db) { 143 super(); 144 145 if (d != null) { 146 m_d = d; 147 } 148 149 m_usr = usr; 150 151 m_p = p; 152 m_p.attach(db); 153 m_p.attach(this); 154 } 155 156 // ProcessContext methods 157 public void setFormSheet(SaleProcess p, FormSheet fs) throws InterruptedException { 158 159 if (fs != null) { 160 fs.attach(p); 161 } 162 163 m_d.setFormSheet(fs); 164 } 165 166 public void popUpFormSheet(SaleProcess p, FormSheet fs) throws InterruptedException { 167 168 if (fs != null) { 169 fs.attach(p); 170 } 171 172 m_d.popUpFormSheet(fs); 173 } 174 175 public void setMenuSheet(SaleProcess p, MenuSheet ms) { 176 if (ms != null) { 177 ms.attach(p); 178 } 179 180 m_d.setMenuSheet(ms); 181 } 182 183 public boolean hasUseableDisplay(SaleProcess p) { 184 return m_d.isUseableDisplay(); 185 } 186 187 public void log(SaleProcess p, Loggable la) throws IOException { 188 Shop.getTheShop().log(la); 189 } 190 191 public User getCurrentUser(SaleProcess p) { 192 return m_usr; 193 } 194 195 public Stock getStock(String sName) { 196 return Shop.getTheShop().getStock(sName); 197 } 198 199 public Catalog getCatalog(String sName) { 200 return Shop.getTheShop().getCatalog(sName); 201 } 202 203 public void processStarted(SaleProcess p) {} 204 205 public void processFinished(SaleProcess p) { 206 p.detachContext(); 207 208 synchronized (Shop.getTheShop().getProcessesLock()) { 209 Shop.getTheShop().m_lphProcesses.remove(this); 210 } 211 } 212 213 // other operations 214 /** 215 * Suspend the process that is handled by this ProcessHandle. 216 * 217 * @override Never 218 */ 219 public void suspend() throws InterruptedException { 220 m_p.suspend(); 221 } 222 223 /** 224 * Resume the process that is handled by this ProcessHandle. 225 * 226 * @override Never 227 */ 228 public void resume() { 229 m_p.resume(); 230 } 231 232 /** 233 * Check whether the process that is handled by this ProcessHandle can be quitted. 234 * 235 * <p>The default implementation simply calls 236 * <pre> 237 * m_p.{@link SaleProcess#canQuit canQuit (fContextDestroy)}; 238 * </pre> 239 * 240 * Called by {@link #canShutdown}.</p> 241 * 242 * @override Sometimes 243 */ 244 public boolean canShutdown(boolean fContextDestroy) { 245 return m_p.canQuit(fContextDestroy); 246 } 247 } 248 249 /** 250 * All remote or background processes currently running on this Shop, represented by their 251 * {@link ProcessHandle process handles}. 252 * 253 * @serial 254 */ 255 protected List m_lphProcesses = new LinkedList(); 256 257 /** 258 * The monitor synchronizing access to the list of processes. 259 */ 260 private transient Object m_oProcessesLock; 261 262 /** 263 * Return the monitor synchronizing access to the list of processes. 264 * 265 * @override Never 266 */ 267 protected final Object getProcessesLock() { 268 if (m_oProcessesLock == null) { 269 m_oProcessesLock = new Object(); 270 } 271 272 return m_oProcessesLock; 273 } 274 275 /** 276 * The global catalogs. 277 * 278 * @serial 279 */ 280 private Map m_mpCatalogs = new HashMap(); 281 282 /** 283 * The monitor synchronizing access to the Catalogs. 284 */ 285 private transient Object m_oCatalogsLock; 286 287 /** 288 * Return the monitor synchronizing access to the Catalogs. 289 * 290 * @override Never 291 */ 292 private final Object getCatalogsLock() { 293 if (m_oCatalogsLock == null) { 294 m_oCatalogsLock = new Object(); 295 } 296 297 return m_oCatalogsLock; 298 } 299 300 /** 301 * The global Catalogs' name context. ATTENTION: Currently rollback and/or commit of Catalog name changes 302 * are not supported. 303 * 304 * @serial 305 */ 306 // This should be done as soon as nested Catalogs are properly implemented. 307 private final NameContext m_ncCatalogContext = new NameContext() { 308 public void checkNameChange(DataBasket db, String sOldName, 309 String sNewName) throws NameContextException { 310 if (db != null) { 311 throw new NameContextException( 312 "Rollback/commit of name changes of global Catalogs not yet implemented."); 313 } 314 315 if (m_mpCatalogs.containsKey(sNewName)) { 316 throw new NameContextException("Name already spent!"); 317 } 318 } 319 320 public void nameHasChanged(DataBasket db, String sOldName, String sNewName) { 321 m_mpCatalogs.put(sNewName, m_mpCatalogs.remove(sOldName)); 322 } 323 324 public Object getNCMonitor() { 325 return getCatalogsLock(); 326 } 327 }; 328 329 /** 330 * The global Stocks. 331 * 332 * @serial 333 */ 334 private Map m_mpStocks = new HashMap(); 335 336 /** 337 * The monitor synchronizing access to the Stocks. 338 */ 339 private transient Object m_oStocksLock; 340 341 /** 342 * Return the monitor synchronizing access to the Stocks. 343 * 344 * @override Never 345 */ 346 private final Object getStocksLock() { 347 if (m_oStocksLock == null) { 348 m_oStocksLock = new Object(); 349 } 350 351 return m_oStocksLock; 352 } 353 354 /** 355 * The global Stocks' name context. ATTENTION: Currently rollback and/or commit of Stock name changes are 356 * not supported. 357 * 358 * @serial 359 */ 360 // This should be done as soon as nested Stocks are properly implemented. 361 private final NameContext m_ncStockContext = new NameContext() { 362 public void checkNameChange(DataBasket db, String sOldName, 363 String sNewName) throws NameContextException { 364 if (db != null) { 365 throw new NameContextException( 366 "Rollback/commit of name changes of global Stocks not yet implemented."); 367 } 368 369 if (m_mpStocks.containsKey(sNewName)) { 370 throw new NameContextException("Name already spent!"); 371 } 372 } 373 374 public void nameHasChanged(DataBasket db, String sOldName, String sNewName) { 375 m_mpStocks.put(sNewName, m_mpStocks.remove(sOldName)); 376 } 377 378 public Object getNCMonitor() { 379 return getStocksLock(); 380 } 381 }; 382 383 /** 384 * The current state of the Shop. One of {@link #DEAD}, {@link #RUNNING} or {@link #SUSPENDED}. 385 * 386 * @serial 387 */ 388 private int m_nShopState = DEAD; 389 390 /** 391 * The monitor synchronizing access to the Shop's state. 392 */ 393 private transient Object m_oStateLock; 394 395 /** 396 * Return the monitor synchronizing access to the Shop's state. 397 * 398 * @override Never 399 */ 400 private final Object getStateLock() { 401 if (m_oStateLock == null) { 402 m_oStateLock = new Object(); 403 } 404 405 return m_oStateLock; 406 } 407 408 /** 409 * The Shop's frame. 410 */ 411 protected transient JFrame m_jfShopFrame = null; 412 413 /** 414 * The title of the Shop's frame. 415 * 416 * @serial 417 */ 418 protected String m_sShopFrameTitle = "Shop"; 419 420 /** 421 * Temporary helper variable to be able to insert the MultiWindow MenuSheet into the Shop's menu. 422 */ 423 private transient MenuSheet m_msMultiWindowMenu; 424 425 /** 426 * The Timer used by this Shop for managing the simulation time. 427 * 428 * @serial 429 */ 430 protected Timer m_trTimer; 431 432 /** 433 * Objects that where registered to be made persistent. 434 * 435 * @serial 436 */ 437 protected Map m_mpToPersistify = new HashMap(); 438 439 /** 440 * The monitor synchronizing access to the persistent objects. 441 */ 442 private transient Object m_oPersistifyLock = null; 443 444 /** 445 * @return the monitor synchronizing access to the persistent objects. 446 * 447 * @override Never 448 */ 449 private final Object getPersistifyLock() { 450 if (m_oPersistifyLock == null) { 451 m_oPersistifyLock = new Object(); 452 } 453 454 return m_oPersistifyLock; 455 } 456 457 /** 458 * First writes the default serializable fields, then calls {@link #onSaveFrames}. 459 */ 460 private void writeObject(ObjectOutputStream oos) throws IOException { 461 util.Debug.print("Writing Shop!", -1); 462 463 synchronized (getPersistifyLock()) { 464 oos.defaultWriteObject(); 465 } 466 467 onSaveFrames(oos); 468 469 util.Debug.print("Finished writing Shop.", -1); 470 } 471 472 /** 473 * First reads the default serializable fields, then calls {@link #onLoadFrames}. 474 */ 475 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 476 util.Debug.print("Loading Shop...", -1); 477 478 // set the Shop to make sure that all content creators etc. use the correct shop!!! 479 setTheShop(this); 480 481 synchronized (getPersistifyLock()) { 482 ois.defaultReadObject(); 483 } 484 485 onLoadFrames(ois); 486 487 util.Debug.print("Finished loading Shop.", -1); 488 } 489 490 /** 491 * Sole constructor to enforce singleton pattern. 492 */ 493 protected Shop() { 494 } 495 496 /** 497 * Add a SalesPoint to the Shop. 498 * 499 * @override Never Instead, override {@link #onSalesPointAdded}. 500 * 501 * @param sp the SalesPoint to be added. 502 */ 503 public void addSalesPoint(final SalesPoint sp) { 504 synchronized (getStateLock()) { 505 if (getShopState() != RUNNING) { 506 try { 507 sp.suspend(); 508 } 509 catch (InterruptedException e) {} 510 } 511 512 synchronized (getSalesPointsLock()) { 513 //check whether this SalesPoint is already added 514 Iterator it = m_lspSalesPoints.iterator(); 515 while (it.hasNext()) { 516 SalesPoint sp_open = (SalesPoint)it.next(); 517 if (sp_open.equals(sp)) { 518 return; 519 } 520 } 521 //if not, add it 522 sp.createNewID(m_lspSalesPoints); 523 m_lspSalesPoints.add(sp); 524 525 /*((MultiWindow)getShopFrame()).addSalesPointDisplay(sp); 526 onSalesPointAdded(sp);*/ 527 try { 528 SwingUtilities.invokeAndWait(new Thread() { 529 public void run() { 530 ((MultiWindow)getShopFrame()).addSalesPointDisplay(sp); 531 onSalesPointAdded(sp); 532 } 533 }); 534 } 535 catch (Exception e) { 536 e.printStackTrace(); 537 } 538 } 539 } 540 } 541 542 private void runAndWait(Thread t) { 543 try { 544 SwingUtilities.invokeLater(t); 545 } 546 catch (Exception ex) { 547 System.err.println("Exception"); 548 ex.printStackTrace(); 549 } 550 551 } 552 553 554 /** 555 * Sets the view mode for the Shop. 556 * @param viewMode can be MultiWindow.WINDOW_VIEW, MultiWindow.TABBED_VIEW, MultiWindow.DESKTOP_VIEW 557 */ 558 public void setViewMode(int viewMode) { 559 ((MultiWindow)getShopFrame()).setViewMode(viewMode); 560 } 561 562 /** 563 * Hook method performing additional work when a SalesPoint was added. 564 * 565 * @override Sometimes Make sure to call the super class's method if overriding this method. 566 * 567 * @param sp the SalesPoint that was removed from the Shop. 568 */ 569 protected void onSalesPointAdded(final SalesPoint sp) { 570 MenuSheet ms = ((MultiWindow)getShopFrame()).getCurrentMenuSheet(); 571 572 if (ms != null) { 573 ms = (MenuSheet)ms.getTaggedItem(SET_CURRENT_SP_TAG); 574 575 if (ms != null) { 576 ms.add(new MenuSheetItem(sp.getName(), 577 "__TAG:_SALESPOINT_SELECTOR_" + sp.getName() + sp.getID(), new Action() { 578 public void doAction(SaleProcess p, SalesPoint _sp) throws IOException { 579 Shop.getTheShop().setCurrentSalesPoint(sp); 580 } 581 })); 582 } 583 } 584 585 setCurrentSalesPoint(sp); 586 sp.logSalesPointOpened(); 587 } 588 589 private String createTag(SalesPoint sp) { 590 Iterator it = getSalesPoints().iterator(); 591 int i = 0; 592 return ""; 593 } 594 595 /** 596 * Remove a SalesPoint from the Shop. 597 * 598 * <p>Prior to being removed from the Shop, the SalesPoint will be 599 * {@link SalesPoint#suspend suspended}.</p> 600 * 601 * @override Never Instead, override {@link #onSalesPointRemoved}. 602 * 603 * @param sp the SalesPoint to be removed 604 */ 605 public void removeSalesPoint(final SalesPoint sp) { 606 try { 607 sp.suspend(); 608 } 609 catch (InterruptedException e) { 610 Thread.currentThread().interrupt(); 611 } 612 613 synchronized (getSalesPointsLock()) { 614 if (m_lspSalesPoints.contains(sp)) { 615 m_lspSalesPoints.remove(sp); 616 617 try { 618 SwingUtilities.invokeAndWait(new Thread() { 619 public void run() { 620 ((MultiWindow)getShopFrame()).closeSalesPointDisplay(sp); 621 onSalesPointRemoved(sp); 622 } 623 }); 624 } 625 catch (Exception e) { 626 e.printStackTrace(); 627 } 628 629 } 630 } 631 } 632 633 /** 634 * Hook method called when a SalesPoint was removed from the Shop. 635 * 636 * @override Sometimes Make sure to call the super class's method if you override this method. 637 * 638 * @param sp the SalesPoint that was removed from the Shop. 639 */ 640 protected void onSalesPointRemoved(SalesPoint sp) { 641 if (getCurrentSalesPoint() == sp) { 642 if (m_lspSalesPoints.size() > 0) { 643 setCurrentSalesPoint((SalesPoint)m_lspSalesPoints.get(0)); 644 } else { 645 setCurrentSalesPoint(null); 646 } 647 } 648 649 MenuSheet ms = ((MultiWindow)getShopFrame()).getCurrentMenuSheet(); 650 651 if (ms != null) { 652 ms = (MenuSheet)ms.getTaggedItem(SET_CURRENT_SP_TAG); 653 654 if (ms != null) { 655 ms.remove("__TAG:_SALESPOINT_SELECTOR_" + sp.getName() + sp.getID()); 656 } 657 } 658 659 sp.logSalesPointClosed(); 660 } 661 662 663 /** 664 * Close a status display. 665 * 666 * @override Never 667 * 668 * @param d the status display to be closed. 669 */ 670 protected void removeStatusDisplay(Display d) { 671 //((MultiWindow)getShopFrame()).closeDisplay(d); 672 } 673 674 /** 675 * Get a list of all SalesPoints in the Shop. 676 * 677 * <p>The list is backed by the SalesPoint's queue, but is immutable.</p> 678 * 679 * @override Never 680 */ 681 public List getSalesPoints() { 682 synchronized (getSalesPointsLock()) { 683 return Collections.unmodifiableList(m_lspSalesPoints); 684 } 685 } 686 687 public SalesPoint getSalesPoint(int id) { 688 Iterator it = getSalesPoints().iterator(); 689 while (it.hasNext()) { 690 SalesPoint sp = (SalesPoint)it.next(); 691 if (sp.getID() == id) { 692 return sp; 693 } 694 } 695 return null; 696 } 697 698 /** 699 * Makes a SalesPoint the current SalesPoint. 700 * 701 * <p>This will bring the specified SalesPoint's window to the front.</p> 702 * 703 * @override Never 704 * 705 * @param sp the SalesPoint to be the current SalesPoint from now on. 706 */ 707 public void setCurrentSalesPoint(SalesPoint sp) { 708 m_spCurrent = sp; 709 if (isCurrentSalesPointAdjusting() || sp == null) { 710 return; 711 } 712 sp.getDisplay().toFront(); 713 } 714 715 /** 716 * Sets a flag that can be used to optimize setCurrentSalesPoint calls. As long as this flag is set, i.e. 717 * {@link #isCurrentSalesPointAdjusting} returns true, calls to {@link #setCurrentSalesPoint} will have no 718 * effect. You can reset the flag by calling {@link #resetCurrentSalesPointIsAdjusting}. 719 * 720 * @override Never 721 */ 722 public void setCurrentSalesPointIsAdjusting() { 723 ++m_nCurrentSalesPointIsAdjusting; 724 } 725 726 /** 727 * Reset a flag that can be used to optimize setCurrentSalesPoint calls. There must be one call to 728 * <code>resetCurrentSalesPointIsAdjusting</code> for each call to {@link #setCurrentSalesPointIsAdjusting}. 729 * Calls to this function must be followed by an explicit call to {@link #setCurrentSalesPoint}. 730 * 731 * @override Never 732 */ 733 public void resetCurrentSalesPointIsAdjusting() { 734 --m_nCurrentSalesPointIsAdjusting; 735 } 736 737 /** 738 * Return whether or not calls to {@link #setCurrentSalesPoint(sale.SalesPoint)} have any effect. 739 * 740 * @override Never 741 */ 742 public boolean isCurrentSalesPointAdjusting() { 743 return m_nCurrentSalesPointIsAdjusting > 0; 744 } 745 746 /** 747 * Returns the currently active SalesPoint of the Shop. 748 * 749 * @override Never 750 */ 751 public SalesPoint getCurrentSalesPoint() { 752 return m_spCurrent; 753 } 754 755 // background process management 756 /** 757 * Run a process on the Shop. 758 * 759 * @override Never 760 * 761 * @param p the process to be run. 762 * @param d the display to be used by the Shop. This can be <code>null</code>, then, a {@link NullDisplay} 763 * will be used. 764 * @param usr the user to be the current user for the process. 765 * @param db the DataBasket to be used by the process. 766 */ 767 public void runProcess(SaleProcess p, Display d, User usr, DataBasket db) { 768 synchronized (getStateLock()) { 769 synchronized (getProcessesLock()) { 770 m_lphProcesses.add(new ProcessHandle(p, d, usr, db)); 771 if (getShopState() == RUNNING) { 772 p.start(); 773 } else { 774 try { 775 p.suspend(); 776 } 777 catch (InterruptedException ie) {} 778 } 779 } 780 } 781 } 782 783 /** 784 * Run a background process on the Shop. A background process does not have a display. You can use 785 * background processes to perform tasks that do not need user communication. 786 * 787 * @override Never 788 * 789 * @param p the process to be run. 790 * @param usr the user to be the current user for the process. 791 * @param db the DataBasket to be used by the process. 792 */ 793 public void runBackgroundProcess(SaleProcess p, User usr, DataBasket db) { 794 runProcess(p, null, usr, db); 795 } 796 797 // Shop state related methods 798 /** 799 * Start the Shop. 800 * 801 * <p>This method must not be called after load up.</p> 802 * 803 * @override Never 804 */ 805 public void start() { 806 synchronized (getStateLock()) { 807 if (getShopState() == DEAD) { 808 JFrame jf = getShopFrame(); 809 810 if (getShopFrameBounds() != null) { 811 jf.setBounds(getShopFrameBounds()); 812 } else { 813 jf.pack(); 814 } 815 816 jf.setVisible(true); 817 818 m_nShopState = SUSPENDED; 819 resume(); 820 } 821 } 822 } 823 824 /** 825 * Sets the Framebounds of the Shops assoziated JFrame. 826 * 827 * <p>Example:<p> 828 * <code>Shop.getTheShop().setShopFrameBounds (new Rectangle (10,10,200,200));<code> 829 * 830 * This moves the JFrame to Position (10,10) with a size of (200,200). 831 * 832 * @override Sometimes 833 */ 834 public void setShopFrameBounds(Rectangle r) { 835 if (getShopState() == DEAD) { 836 m_rShopFrameBounds = r; 837 } else { 838 if ((m_rShopFrameBounds != null) && (getShopState() == RUNNING)) { 839 m_rShopFrameBounds = r; 840 getShopFrame().setBounds(r); 841 getShopFrame().hide(); 842 getShopFrame().show(); 843 } 844 } 845 } 846 847 /** 848 * Returns the Framebounds of the Shops assoziated JFrame. 849 * 850 * @override Sometimes 851 */ 852 public Rectangle getShopFrameBounds() { 853 return m_rShopFrameBounds; 854 } 855 856 /** 857 * Suspend a running Shop. Suspending the Shop includes suspending all SalesPoints currently in the Shop. 858 * 859 * @override Never 860 */ 861 public void suspend() { 862 synchronized (getStateLock()) { 863 if (getShopState() == RUNNING) { 864 865 // suspend all remote processes 866 synchronized (getProcessesLock()) { 867 for (Iterator i = m_lphProcesses.iterator(); i.hasNext(); ) { 868 try { 869 ((ProcessHandle)i.next()).suspend(); 870 } 871 catch (InterruptedException ie) {} 872 } 873 } 874 875 // suspend all SalesPoints 876 synchronized (getSalesPointsLock()) { 877 for (Iterator i = m_lspSalesPoints.iterator(); i.hasNext(); ) { 878 try { 879 ((SalesPoint)i.next()).suspend(); 880 } 881 catch (InterruptedException e) {} 882 } 883 } 884 885 m_nShopState = SUSPENDED; 886 } 887 } 888 } 889 890 /** 891 * Resume a suspended Shop. Resuming includes resuming all SalesPoints currently in the Shop. 892 * 893 * @override Never 894 */ 895 public void resume() { 896 synchronized (getStateLock()) { 897 if (getShopState() == SUSPENDED) { 898 899 // resume all remote processes 900 synchronized (getProcessesLock()) { 901 for (Iterator i = m_lphProcesses.iterator(); i.hasNext(); ) { 902 ((ProcessHandle)i.next()).resume(); 903 } 904 } 905 906 // resume all SalesPoints 907 synchronized (getSalesPointsLock()) { 908 for (Iterator i = m_lspSalesPoints.iterator(); i.hasNext(); ) { 909 SalesPoint sp = (SalesPoint)i.next(); 910 /*JDisplayFrame jdf = (JDisplayFrame)sp.getDisplay(); 911 jdf.setVisible(true);*/sp.resume(); 912 } 913 } 914 915 m_nShopState = RUNNING; 916 } 917 } 918 } 919 920 /** 921 * Close the Shop and quit the application. 922 * 923 * 924 * <p>This method is linked to the "Quit" item in the Shop's MenuSheet as well as to the close 925 * window gesture for the Shop frame.</p> 926 * 927 * @override Sometimes By default implemented as: 928 * <pre> 929 * if (Shop.{@link #getTheShop getTheShop()}.{@link #shutdown shutdown (true)}) { 930 * System.exit (0); 931 * }; 932 * </pre> 933 */ 934 public void quit() { 935 if (Shop.getTheShop().shutdown(true)) { 936 System.exit(0); 937 } 938 } 939 940 /** 941 * Close the Shop. 942 * 943 * <p>Calling this method will stop any processes currently running on any SalesPoints in 944 * the Shop after calling {@link #canShutdown} to check whether shutdown is permitted at 945 * the moment. The method must therefore not be called from within a process !</p> 946 * 947 * @override Never 948 * 949 * @param fPersistify if true, the current state of the Shop will be made persistent prior 950 * to actually closing the Shop. 951 * 952 * @return true if the shutdown was successful. 953 */ 954 public boolean shutdown(boolean fPersistify) { 955 synchronized (getSalesPointsLock()) { 956 synchronized (getProcessesLock()) { 957 boolean fRunning = (getShopState() == RUNNING); 958 959 if (!canShutdown(fPersistify)) { 960 return false; 961 } 962 if (fPersistify) { 963 try { 964 makePersistent(); 965 } 966 catch (CancelledException ce) { 967 if (fRunning) { 968 resume(); 969 } 970 return false; 971 } 972 catch (Throwable t) { 973 System.err.println("Exception occurred while making persistent: " + t); 974 t.printStackTrace(); 975 976 if (fRunning) { 977 resume(); 978 } 979 980 return false; 981 } 982 } 983 984 clearInternalStructures(); 985 986 m_nShopState = DEAD; 987 988 return true; 989 } 990 } 991 } 992 993 /** 994 * Check whether shutdown can be permitted in the current state of the system. 995 * 996 * <p>In this method you can assume that you are the owner of {@link #getSalesPointsLock()} 997 * and {@link #getProcessesLock()}, so that you can access the list of SalesPoints and the 998 * list of processes without extra synchronization.</p> 999 * 1000 * <p>The default implementation will first {@link #suspend} the Shop, should 1001 * {@link #getShopState its state} be {@link #RUNNING}. It will then check all processes running on the 1002 * Shop. If no such processes exist or if all of them confirm that shut down is possible, it will call the 1003 * {@link SalesPoint#canQuit} method of any {@link SalesPoint} in the system, passing 1004 * <code>!fPersistify</code> as the parameter. If all SalesPoints return true, the Shop stays suspended and 1005 * <code>canShutdown</code> returns true. In any other case, the Shop will be {@link #resume resumed} again, 1006 * and false will be returned.</p> 1007 * 1008 * <p>This method is usually not called directly, but if you do, make sure to call it 1009 * with synchronization on {@link #getSalesPointsLock()} and {@link #getProcessesLock()}.</p> 1010 * 1011 * @override Sometimes 1012 * 1013 * @param fPersistify if true, the Shop's state will be made persistent before shutdown. 1014 * 1015 * @return true to indicate shutdown is OK; false otherwise. 1016 */ 1017 protected boolean canShutdown(boolean fPersistify) { 1018 boolean fRunning = (getShopState() == RUNNING); 1019 1020 if (fRunning) { 1021 suspend(); 1022 } 1023 1024 boolean fCanQuit = true; 1025 1026 // check for background or remote processes 1027 for (Iterator i = m_lphProcesses.iterator(); i.hasNext() && fCanQuit; ) { 1028 fCanQuit = ((ProcessHandle)i.next()).canShutdown(!fPersistify); 1029 } 1030 1031 // check for SalesPoints 1032 for (Iterator i = m_lspSalesPoints.iterator(); i.hasNext() && fCanQuit; ) { 1033 fCanQuit = ((SalesPoint)i.next()).canQuit(!fPersistify); 1034 } 1035 1036 if (!fCanQuit) { 1037 if (fRunning) { 1038 resume(); 1039 } 1040 1041 return false; 1042 } 1043 1044 // all fine... 1045 return true; 1046 } 1047 1048 /** 1049 * Return the Shop's state, being one of {@link #DEAD}, {@link #RUNNING} or {@link #SUSPENDED}. 1050 * 1051 * @override Never 1052 */ 1053 public int getShopState() { 1054 return m_nShopState; 1055 } 1056 1057 /** 1058 * Make the current state of the Shop persistent. 1059 * 1060 * @override Never 1061 * 1062 * @exception IOException if an error occurred. 1063 * @exception CancelledException if the retrieval of the persistence stream was cancelled by the user. 1064 */ 1065 public synchronized void makePersistent() throws IOException, CancelledException { 1066 boolean fRunning = (getShopState() == RUNNING); 1067 if (fRunning) { 1068 suspend(); 1069 } 1070 try { 1071 OutputStream osStream = retrievePersistenceOutStream(); 1072 1073 synchronized (getSalesPointsLock()) { 1074 synchronized (getProcessesLock()) { 1075 1076 ObjectOutputStream oosOut = new ObjectOutputStream(osStream); 1077 1078 oosOut.writeObject(this); 1079 oosOut.writeObject(UserManager.getGlobalUM()); 1080 oosOut.writeObject(User.getGlobalPassWDGarbler()); 1081 //save global log file (if desired) 1082 /*File f = Log.getGlobalLogFile(); 1083 if (f != null && Log.getSaveToPersistence()) { 1084 FileInputStream fis = new FileInputStream(f); 1085 copy(fis, osStream); 1086 }*/File f = Log.getGlobalLogFile(); 1087 if (f != null && Log.getSaveToPersistence()) { 1088 oosOut.writeObject(new LogFileContent(f)); 1089 } 1090 1091 oosOut.flush(); 1092 oosOut.close(); 1093 osStream.close(); 1094 } 1095 } 1096 } 1097 finally { 1098 if (fRunning) { 1099 resume(); 1100 } 1101 } 1102 } 1103 1104 /** 1105 * Save the Shop's main frame's and the status frame's state to the given stream. 1106 * 1107 * @override Never 1108 * 1109 * @param oos the Stream to save to 1110 * 1111 * @exception IOException if an error occurred while saving the frames' states. 1112 */ 1113 protected void onSaveFrames(ObjectOutputStream oos) throws IOException { 1114 ((MultiWindow)getShopFrame()).save(oos); 1115 1116 // Save all SalesPoints' displays 1117 for (Iterator i = m_lspSalesPoints.iterator(); i.hasNext(); ) { 1118 ((SalesPoint)i.next()).getDisplay().save(oos); 1119 } 1120 } 1121 1122 /** 1123 * Restore the Shop's state from a Stream. 1124 * 1125 * <p><strong>Attention:</strong> Any old reference to the Shop is invalid afterwards. The new Shop can be 1126 * acquired through {@link #getTheShop Shop.getTheShop()}.</p> 1127 * 1128 * @override Never 1129 * 1130 * @exception IOException if an exception occurred while loading 1131 * @exception ClassNotFoundException if an exception occurred while loading 1132 * @exception CancelledException if the user cancels loading. 1133 */ 1134 public synchronized void restore() throws IOException, ClassNotFoundException, CancelledException { 1135 1136 InputStream isStream = retrievePersistenceInStream(); 1137 1138 if (!shutdown(false)) { 1139 throw new CancelledException(); 1140 } 1141 1142 synchronized (getSalesPointsLock()) { 1143 synchronized (getProcessesLock()) { 1144 1145 ObjectInputStream oisIn = new ObjectInputStream(isStream); 1146 // Setzt den Shop automatisch neu !!! 1147 oisIn.readObject(); 1148 UserManager.setGlobalUM((UserManager)oisIn.readObject()); 1149 User.setGlobalPassWDGarbler((users.PassWDGarbler)oisIn.readObject()); 1150 //create new logfile and load saved logs from persistence file (if they exist) into it 1151 File f = Log.getGlobalLogFile(); 1152 if (f != null && Log.getSaveToPersistence()) { 1153 Log.setGlobalLogFile(f.getName(), true, true); 1154 //FileOutputStream fos = new FileOutputStream(Log.getGlobalLogFile()); 1155 //copy(isStream, fos); 1156 try { 1157 LogFileContent lfc = (LogFileContent)oisIn.readObject(); 1158 1159 Log.getGlobalLog().addLogEntries(lfc); 1160 } 1161 catch (Exception e) { 1162 } 1163 } 1164 oisIn.close(); 1165 isStream.close(); 1166 } 1167 } 1168 1169 synchronized (getTheShop().getStateLock()) { 1170 /*for (Iterator it = getTheShop().getSalesPoints().iterator(); it.hasNext();) { 1171 getTheShop().onSalesPointAdded((SalesPoint)it.next()); 1172 }*/ 1173 getTheShop().m_nShopState = SUSPENDED; 1174 getTheShop().resume(); 1175 } 1176 } 1177 1178 /** 1179 * Copies bytes from an InputStream to an OutputStream 1180 */ 1181 private void copy(InputStream in, OutputStream out) { 1182 synchronized (in) { 1183 synchronized (out) { 1184 byte[] buffer = new byte[256]; 1185 while (true) { 1186 try { 1187 int bytesread = in.read(buffer); 1188 if (bytesread == -1) { 1189 break; 1190 } 1191 out.write(buffer, 0, bytesread); 1192 } 1193 catch (IOException ioe) { 1194 ioe.printStackTrace(); 1195 } 1196 } 1197 } 1198 } 1199 } 1200 1201 /** 1202 * Loads the Shop's main frame and the SalesPoints' frames' states from the given stream. 1203 * 1204 * @override Never 1205 * 1206 * @param ois the Stream to load from 1207 * 1208 * @exception IOException if an error occurred while loading the frames' states. 1209 * @exception ClassNotFoundException if an error occurred while loading the frames' states. 1210 */ 1211 protected void onLoadFrames(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1212 ((MultiWindow)getShopFrame()).load(ois); 1213 1214 // Load all SalesPoints' displays 1215 for (Iterator i = m_lspSalesPoints.iterator(); i.hasNext(); ) { 1216 SalesPoint sp = (SalesPoint)i.next(); 1217 1218 Class c = (Class)ois.readObject(); 1219 Display d = null; 1220 MultiWindow mw = (MultiWindow)getShopFrame(); 1221 //is saved class a DisplayFrame or a subclass of DisplayFrame? 1222 if (MultiWindow.DisplayFrame.class.isAssignableFrom(c)) { 1223 d = mw.getNewWindow(sp); 1224 } 1225 //is saved class a TabbedFrame or a subclass of TabbedFrame? 1226 if (MultiWindow.TabbedFrame.class.isAssignableFrom(c)) { 1227 d = mw.getNewTab(sp); 1228 } 1229 //is saved class a DesktopFrame or a subclass of DesktopFrame? 1230 if (MultiWindow.DesktopFrame.class.isAssignableFrom(c)) { 1231 d = mw.getNewInternalFrame(sp); 1232 } 1233 d.load(ois); 1234 sp.attachLoadedDisplay(d); 1235 } 1236 } 1237 1238 /** 1239 * Helper method creating the dialog in which the user can select the persistence file. 1240 * 1241 * @override Never 1242 */ 1243 private JFileChooser getChooser() { 1244 JFileChooser jfcChooser = new JFileChooser(); 1245 1246 jfcChooser.setFileFilter(new javax.swing.filechooser.FileFilter() { 1247 public boolean accept(File fToAccept) { 1248 if (fToAccept == null) { 1249 return false; 1250 } 1251 1252 if (fToAccept.isDirectory()) { 1253 return true; 1254 } 1255 1256 StringTokenizer stName = new StringTokenizer(fToAccept.getName(), "."); 1257 1258 if (stName.hasMoreTokens()) { 1259 stName.nextToken(); 1260 } else { 1261 return false; 1262 } 1263 1264 String sSuffix = null; 1265 while (stName.hasMoreTokens()) { 1266 sSuffix = stName.nextToken(); 1267 } 1268 1269 if (sSuffix != null) { 1270 return (sSuffix.toLowerCase().equals("prs")); 1271 } else { 1272 return false; 1273 } 1274 } 1275 1276 public String getDescription() { 1277 return "Persistence Files (*.prs)"; 1278 } 1279 }); 1280 1281 jfcChooser.setFileSelectionMode(javax.swing.JFileChooser.FILES_ONLY); 1282 jfcChooser.setMultiSelectionEnabled(false); 1283 1284 return jfcChooser; 1285 } 1286 1287 /** 1288 * Retrieves the stream to which the Shop's state is to be written. 1289 * 1290 * @override Sometimes The default implementation allows the user to select a file name and creates a stream 1291 * for the specified file. 1292 * 1293 * @exception IOException if an exception occurred while creating the stream 1294 * @exception CancelledException if the user cancelled the save process. 1295 */ 1296 protected OutputStream retrievePersistenceOutStream() throws IOException, CancelledException { 1297 javax.swing.JFileChooser jfcChooser = getChooser(); 1298 1299 File fFile = null; 1300 1301 do { 1302 if (jfcChooser.showSaveDialog(null) == JFileChooser.CANCEL_OPTION) { 1303 throw new CancelledException("File choosing cancelled."); 1304 } 1305 1306 fFile = jfcChooser.getSelectedFile(); 1307 1308 if (fFile == null) { 1309 throw new CancelledException("No file selected."); 1310 } 1311 1312 if (!jfcChooser.getFileFilter().accept(fFile) && !fFile.exists()) { 1313 fFile = new File(fFile.getParent(), fFile.getName() + ".prs"); 1314 1315 } 1316 if ((jfcChooser.accept(fFile)) && (!fFile.exists())) { 1317 switch (JOptionPane.showConfirmDialog(null, 1318 fFile.getAbsolutePath() + " does not exist.\nCreate?", "Confirmation", 1319 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) { 1320 case JOptionPane.NO_OPTION: 1321 fFile = null; 1322 break; 1323 1324 case JOptionPane.CANCEL_OPTION: 1325 throw new CancelledException("File choosing cancelled."); 1326 1327 case JOptionPane.YES_OPTION: 1328 fFile.createNewFile(); 1329 } 1330 } 1331 1332 } 1333 while (!jfcChooser.getFileFilter().accept(fFile) || fFile.isDirectory()); 1334 1335 return new java.io.FileOutputStream(fFile); 1336 } 1337 1338 /** 1339 * Retrieves the stream from which to read the Shop's state. 1340 * 1341 * @override Sometimes The default implementation allows the user to select a file name and creates a stream 1342 * for the specified file. 1343 * 1344 * @exception IOException if an exception occurred while creating the stream 1345 * @exception CancelledException if the user cancelled the save process. 1346 */ 1347 protected InputStream retrievePersistenceInStream() throws IOException, CancelledException { 1348 javax.swing.JFileChooser jfcChooser = getChooser(); 1349 1350 do { 1351 jfcChooser.getSelectedFile(); 1352 1353 if (jfcChooser.showOpenDialog(null) == javax.swing.JFileChooser.CANCEL_OPTION) { 1354 throw new CancelledException("File choosing cancelled."); 1355 } 1356 1357 } 1358 while (!jfcChooser.getSelectedFile().exists()); 1359 1360 return new java.io.FileInputStream(jfcChooser.getSelectedFile()); 1361 } 1362 1363 /** 1364 * Sets an object to be persistent. The object can be accessed at the given key. 1365 * 1366 * @override Never 1367 * 1368 * @param oKey the key at which the object can be accessed. 1369 * @param oToPersistify the object that is to be made persistent. 1370 * 1371 * @return the object previously stored at that key. 1372 */ 1373 public Object setObjectPersistent(Object oKey, Object oToPersistify) { 1374 synchronized (getPersistifyLock()) { 1375 Object oReturn = m_mpToPersistify.remove(oKey); 1376 m_mpToPersistify.put(oKey, oToPersistify); 1377 return oReturn; 1378 } 1379 } 1380 1381 /** 1382 * Set an object to be no longer persistent. 1383 * 1384 * @override Never 1385 * 1386 * @param oKey the key at which the object can be accessed. 1387 * 1388 * @return the object that was made transient. 1389 */ 1390 public Object setObjectTransient(Object oKey) { 1391 synchronized (getPersistifyLock()) { 1392 return m_mpToPersistify.remove(oKey); 1393 } 1394 } 1395 1396 /** 1397 * Get a persistent object. 1398 * 1399 * @override Never 1400 * 1401 * @param oKey the key that describes the object. 1402 * 1403 * @return the persistent object. 1404 */ 1405 public Object getPersistentObject(Object oKey) { 1406 synchronized (getPersistifyLock()) { 1407 return m_mpToPersistify.get(oKey); 1408 } 1409 } 1410 1411 /** 1412 * Get an iterator of all persistent objects. You can use the iterator's remove() method to make objects 1413 * transient. 1414 * 1415 * @override Never 1416 */ 1417 public Iterator getPersistentObjects() { 1418 synchronized (getPersistifyLock()) { 1419 return m_mpToPersistify.values().iterator(); 1420 } 1421 } 1422 1423 /** 1424 * Clear the internal structures maintained by the Shop, thus finishing off shutdown. 1425 * 1426 * @override Never 1427 */ 1428 protected void clearInternalStructures() { 1429 synchronized (getSalesPointsLock()) { 1430 while (m_lspSalesPoints.size() > 0) { 1431 removeSalesPoint((SalesPoint)m_lspSalesPoints.get(0)); 1432 } 1433 } 1434 1435 synchronized (getProcessesLock()) { 1436 m_lphProcesses.clear(); 1437 } 1438 1439 // clear and close displays 1440 if (m_jfShopFrame != null) { 1441 m_jfShopFrame.setVisible(false); 1442 m_jfShopFrame.dispose(); 1443 m_jfShopFrame = null; 1444 } 1445 } 1446 1447 /** 1448 * Set the Shop frame's title. Initially, this is "Shop". 1449 * 1450 * @override Never 1451 * 1452 * @param sTitle the new title. 1453 */ 1454 public void setShopFrameTitle(String sTitle) { 1455 m_sShopFrameTitle = sTitle; 1456 getShopFrame().setTitle(sTitle); 1457 } 1458 1459 public String getShopFrameTitle() { 1460 return m_sShopFrameTitle; 1461 } 1462 1463 /** 1464 * Gets the Shop's main frame. 1465 * 1466 * <p>The main Shop frame will be the frame in which the Shop's menu gets displayed.</p> 1467 * 1468 * <p>By default this creates a {@link sale.multiwindow.MultiWindow} with the title that you specified 1469 * in a call to {@link #setShopFrameTitle}.</p> 1470 * 1471 * @override Never, use {@link #createShopFrame} instead 1472 */ 1473 protected JFrame getShopFrame() { 1474 if (m_jfShopFrame == null) { 1475 MultiWindow mw = createShopFrame(); 1476 m_msMultiWindowMenu = mw.getMultiWindowMenuSheet(); 1477 MenuSheet ms = createShopMenuSheet(); 1478 m_msMultiWindowMenu = null; 1479 mw.setMenuSheet(ms); 1480 1481 mw.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); 1482 mw.addWindowListener(new WindowAdapter() { 1483 public void windowClosing(WindowEvent e) { 1484 new Thread("Shop closer") { 1485 public void run() { 1486 Shop.getTheShop().quit(); 1487 } 1488 } 1489 1490 .start(); 1491 } 1492 }); 1493 1494 m_jfShopFrame = mw; 1495 } 1496 return m_jfShopFrame; 1497 } 1498 1499 /** 1500 * Creates and returns a new {@link MultiWindow} with window view mode set. 1501 * 1502 * @override Sometimes If you want to customize the Shop frame create and return yours here. The customized 1503 * Shop frame must be a MultiWindow or a subclass of it. 1504 */ 1505 protected MultiWindow createShopFrame() { 1506 return new MultiWindow(this, MultiWindow.WINDOW_VIEW); 1507 } 1508 1509 /** 1510 * Create and return the Shop's main MenuSheet. 1511 * 1512 * <p>The default implementation will provide two MenuSheets in the Shop's MenuSheet:</p> 1513 * 1514 * <table border> 1515 * <tr> 1516 * <th>MenuSheet (name/tag)</th> 1517 * <th>Item text</th> 1518 * <th>Item tag</th> 1519 * <th>Item action</th> 1520 * <th>Comments</th> 1521 * </tr> 1522 * <tr> 1523 * <td rowspan=7>Shop {@link #SHOP_MENU_TAG}</td> 1524 * <td>Set current SalesPoint</td> 1525 * <td>{@link #SET_CURRENT_SP_TAG}</td> 1526 * <td>{@link #setCurrentSalesPoint setCurrentSalesPoint()}.</td> 1527 * <td>This is a Sub-MenuSheet that shows all the SalesPoints in the Shop. The user can click the one 1528 * he or she wants to select. As long as this MenuSheet is found in the Shop's MenuSheet, it will 1529 * be updated by calls to {@link #addSalesPoint} and {@link #removeSalesPoint}. 1530 * </td> 1531 * </tr> 1532 * <tr> 1533 * <td><i>Separator</i></td> 1534 * <td>{@link #SEPARATOR_ONE_TAG}</td> 1535 * <td></td> 1536 * <td></td> 1537 * </tr> 1538 * <tr> 1539 * <td>Load...</td> 1540 * <td>{@link #LOAD_TAG}</td> 1541 * <td>Load a persistent Shop image.</td> 1542 * <td></td> 1543 * </tr> 1544 * <tr> 1545 * <td>Save...</td> 1546 * <td>{@link #SAVE_TAG}</td> 1547 * <td>Save current Shop state to create a persistant Shop image.</td> 1548 * <td></td> 1549 * </tr> 1550 * <tr> 1551 * <td><i>Separator</i></td> 1552 * <td>{@link #SEPARATOR_TWO_TAG}</td> 1553 * <td></td> 1554 * <td></td> 1555 * </tr> 1556 * <tr> 1557 * <td>Quit</td> 1558 * <td>{@link #QUIT_SHOP_TAG}</td> 1559 * <td>{@link #quit}.</td> 1560 * <td></td> 1561 * </tr> 1562 * <tr> 1563 * <td>MultiWindow {@link sale.multiwindow.MultiWindow#MULTIWINDOW_MENU_TAG}</td> 1564 * <td>see {@link sale.multiwindow.MultiWindow#getMultiWindowMenuSheet}</td> 1565 * <td></td> 1566 * <td></td> 1567 * </tr> 1568 * </table> 1569 * 1570 * @override Sometimes 1571 */ 1572 protected MenuSheet createShopMenuSheet() { 1573 MenuSheet msBar = new MenuSheet("Shop Menu"); 1574 MenuSheet msShop = new MenuSheet("Shop", SHOP_MENU_TAG, 'S'); 1575 //current SalesPoint 1576 MenuSheet msCurrent = new MenuSheet("Set current SalesPoint", SET_CURRENT_SP_TAG); 1577 //load 1578 MenuSheetItem msiLoad = new MenuSheetItem("Load...", LOAD_TAG, new sale.Action() { 1579 public void doAction(SaleProcess p, SalesPoint sp) throws Throwable { 1580 try { 1581 restore(); 1582 } 1583 catch (CancelledException cexc) { 1584 JOptionPane.showMessageDialog(null, cexc.getMessage(), "Loading cancelled", 1585 JOptionPane.ERROR_MESSAGE); 1586 } 1587 } 1588 }); 1589 msiLoad.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); 1590 msiLoad.setMnemonic('L'); 1591 msiLoad.setDefaultIcon(LOAD_ICON); 1592 //save 1593 MenuSheetItem msiSave = new MenuSheetItem("Save...", SAVE_TAG, new sale.Action() { 1594 public void doAction(SaleProcess p, SalesPoint sp) throws Throwable { 1595 try { 1596 makePersistent(); 1597 } 1598 catch (CancelledException cexc) { 1599 JOptionPane.showMessageDialog(null, cexc.getMessage(), "Saving cancelled", 1600 JOptionPane.ERROR_MESSAGE); 1601 } 1602 } 1603 }); 1604 msiSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK)); 1605 msiSave.setMnemonic('S'); 1606 msiSave.setDefaultIcon(SAVE_ICON); 1607 //quit 1608 MenuSheetItem msiQuit = new MenuSheetItem("Quit", QUIT_SHOP_TAG, new sale.Action() { 1609 public void doAction(SaleProcess p, SalesPoint sp) { 1610 quit(); 1611 } 1612 }); 1613 msiQuit.setMnemonic('Q'); 1614 //put menu together 1615 msShop.add(msCurrent); 1616 msShop.add(new MenuSheetSeparator(SEPARATOR_ONE_TAG)); 1617 msShop.add(msiLoad); 1618 msShop.add(msiSave); 1619 msShop.add(new MenuSheetSeparator(SEPARATOR_TWO_TAG)); 1620 msShop.add(msiQuit); 1621 //add shop menu to menu bar 1622 msBar.add(msShop); 1623 //add view mode menu to menu bar 1624 if (m_msMultiWindowMenu != null) { 1625 msBar.add(m_msMultiWindowMenu); 1626 } 1627 return msBar; 1628 } 1629 1630 /** 1631 * Get the Shop's timer. If no timer has been set using {@link #setTimer}, the default timer will be a 1632 * {@link StepTimer} with a {@link Step} time. 1633 * 1634 * @override Never 1635 * 1636 * @return the Shop's Timer 1637 */ 1638 public Timer getTimer() { 1639 if (m_trTimer == null) { 1640 m_trTimer = new StepTimer(); 1641 } 1642 return m_trTimer; 1643 } 1644 1645 /** 1646 * Set the Shop's Timer. 1647 * 1648 * @override Never 1649 * 1650 * @param trTimer the Timer to be used from now on 1651 */ 1652 public void setTimer(Timer trTimer) { 1653 m_trTimer = trTimer; 1654 } 1655 1656 /** 1657 * Log a piece of information to the global log file. 1658 * 1659 * @override Never 1660 * 1661 * @param la the information to be logged. 1662 * 1663 * @exception IOException on any error while logging. 1664 */ 1665 public void log(Loggable la) throws IOException { 1666 Log.getGlobalLog().log(la); 1667 } 1668 1669 /// Stock management 1670 1671 /** 1672 * Add a Stock to the global list of Stocks. The Stock can later be identified by its name. 1673 * 1674 * @override Never 1675 * 1676 * @param st the Stock to be added to the global list of Stocks. 1677 * 1678 * @exception DuplicateKeyException if a Stock of the same name already exists in the global list of Stocks. 1679 */ 1680 public void addStock(Stock st) throws DuplicateKeyException { 1681 synchronized (getStocksLock()) { 1682 if (m_mpStocks.containsKey(st.getName())) { 1683 throw new DuplicateKeyException(st.getName()); 1684 } 1685 1686 m_mpStocks.put(st.getName(), st); 1687 st.attach(m_ncStockContext); 1688 } 1689 } 1690 1691 /** 1692 * Remove a Stock from the global list of Stocks. 1693 * 1694 * @override Never 1695 * 1696 * @param sName the name of the Stock to be removed. 1697 * 1698 * @return the removed Stock, if any. 1699 */ 1700 public Stock removeStock(String sName) { 1701 synchronized (getStocksLock()) { 1702 Stock st = (Stock)m_mpStocks.remove(sName); 1703 1704 if (st != null) { 1705 st.detachNC(); 1706 } 1707 1708 return st; 1709 } 1710 } 1711 1712 /** 1713 * Look up a Stock in the global Stock list. 1714 * 1715 * @override Never 1716 * 1717 * @param sName the name of the Stock to be looked up. 1718 * 1719 * @return the Stock, if any. 1720 */ 1721 public Stock getStock(String sName) { 1722 synchronized (getStocksLock()) { 1723 return (Stock)m_mpStocks.get(sName); 1724 } 1725 } 1726 1727 /// Catalog management 1728 1729 /** 1730 * Add a Catalog to the global table of Catalogs. The Catalog will be identifiable by its name. 1731 * 1732 * @override Never 1733 * 1734 * @param c the Catalog to be added to the global list of Catalogs 1735 * 1736 * @exception DuplicateKeyException if a Catalog of the same name already existed in the global list of 1737 * Catalogs. 1738 */ 1739 public void addCatalog(Catalog c) throws DuplicateKeyException { 1740 synchronized (getCatalogsLock()) { 1741 if (m_mpCatalogs.containsKey(c.getName())) { 1742 throw new DuplicateKeyException(c.getName()); 1743 } 1744 1745 m_mpCatalogs.put(c.getName(), c); 1746 c.attach(m_ncCatalogContext); 1747 } 1748 } 1749 1750 /** 1751 * Remove a catalog from the global table of Catalogs. 1752 * 1753 * @override Never 1754 * 1755 * @param sName the name of the Catalog to be removed. 1756 * 1757 * @return the Catalog that was removed, if any. 1758 */ 1759 public Catalog removeCatalog(String sName) { 1760 synchronized (getCatalogsLock()) { 1761 Catalog c = (Catalog)m_mpCatalogs.remove(sName); 1762 1763 if (c != null) { 1764 c.detachNC(); 1765 } 1766 1767 return c; 1768 } 1769 } 1770 1771 /** 1772 * Get a Catalog from the global list of Catalogs. 1773 * 1774 * @override Never 1775 * 1776 * @param sName the name of the Catalog to be returned. 1777 * 1778 * @return the associated Catalog, if any. 1779 */ 1780 public Catalog getCatalog(String sName) { 1781 synchronized (getCatalogsLock()) { 1782 return (Catalog)m_mpCatalogs.get(sName); 1783 } 1784 } 1785 1786 //////////////////////////////////////////////////////////////////////////////////////////////// 1787 // STATIC PART 1788 //////////////////////////////////////////////////////////////////////////////////////////////// 1789 1790 /** 1791 * Constant marking the Shop's state. DEAD means the Shop was either shut down or not started yet. 1792 */ 1793 public final static int DEAD = 0; 1794 1795 /** 1796 * Constant marking the Shop's state. RUNNING means the Shop was started and neither suspended nor shutdown. 1797 */ 1798 public final static int RUNNING = 1; 1799 1800 /** 1801 * Constant marking the Shop's state. SUSPENDED means the Shop was {@link #suspend suspended}. 1802 */ 1803 public final static int SUSPENDED = 2; 1804 1805 /** 1806 * MenuSheetObject tag marking the entire Shop MenuSheet. 1807 */ 1808 public static final String SHOP_MENU_TAG = "__TAG:_SHOP_MENU_"; 1809 1810 /** 1811 * MenuSheetObject tag marking the "Set Current SalesPoint" item. 1812 */ 1813 public static final String SET_CURRENT_SP_TAG = "__TAG:_SHOP_SET_CURRENT_SALESPOINT_"; 1814 1815 /** 1816 * MenuSheetObject tag marking the first separator. 1817 */ 1818 public static final String SEPARATOR_ONE_TAG = "__TAG:_SHOP_SEPARATOR_1_"; 1819 1820 /** 1821 * MenuSheetObject tag marking the "Load..." item. 1822 */ 1823 public static final String LOAD_TAG = "__TAG:_SHOP_LOAD_"; 1824 1825 /** 1826 * MenuSheetObject tag marking the "Save..." item. 1827 */ 1828 public static final String SAVE_TAG = "__TAG:_SHOP_SAVE_"; 1829 1830 /** 1831 * MenuSheetObject tag marking the second separator. 1832 */ 1833 public static final String SEPARATOR_TWO_TAG = "__TAG:_SHOP_SEPARATOR_2_"; 1834 1835 /** 1836 * MenuSheetObject tag marking the "Quit" item. 1837 */ 1838 public static final String QUIT_SHOP_TAG = "__TAG:_SHOP_QUIT_"; 1839 1840 /** 1841 * Icon MenuItem "Load". 1842 */ 1843 private static final ImageIcon LOAD_ICON = new ImageIcon(ResourceManager.getInstance().getResource( 1844 ResourceManager.RESOURCE_GIF, "icon.icon_load_16x16")); 1845 1846 /** 1847 * Icon MenuItem "Save". 1848 */ 1849 private static final ImageIcon SAVE_ICON = new ImageIcon(ResourceManager.getInstance().getResource( 1850 ResourceManager.RESOURCE_GIF, "icon.icon_save_16x16")); 1851 1852 /** 1853 * The singleton instance of the Shop, that is used throughout the entire application. 1854 */ 1855 private static Shop s_shTheShop; 1856 /** 1857 * The monitor used to synchronized access to the singleton. 1858 */ 1859 private static Object s_oShopLock = new Object(); 1860 1861 /** 1862 * Get the global, singleton Shop instance. 1863 */ 1864 public static Shop getTheShop() { 1865 synchronized (s_oShopLock) { 1866 if (s_shTheShop == null) { 1867 setTheShop(new Shop()); 1868 } 1869 1870 return s_shTheShop; 1871 } 1872 } 1873 1874 /** 1875 * Set the global, singleton Shop instance. 1876 * 1877 * <p>This method will only have an effect the next time, {@link #getTheShop} gets called. 1878 * So to avoid inconsistency, use this method only in the beginning of your program, to 1879 * install an instance of a subclass of Shop as the global, singleton Shop instance.</p> 1880 * 1881 * @param shTheShop the new global, singleton Shop instance 1882 */ 1883 public static void setTheShop(Shop shTheShop) { 1884 synchronized (s_oShopLock) { 1885 s_shTheShop = shTheShop; 1886 } 1887 } 1888 }