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