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