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