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