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