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