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