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