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