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