ec49997b58c83c777bd5701e977d5854c2ee0b25
[jalview.git] / src / jalview / bin / ApplicationSingletonProvider.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import jalview.util.Platform;
24 import jalview.bin.Console;
25
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 /**
32  * A class to hold singleton objects, whose scope (context) is
33  * <ul>
34  * <li>the Java runtime (JVM) when running as Java</li>
35  * <li>one 'applet', when running as JalviewJS</li>
36  * </ul>
37  * This allows separation of multiple JS applets running on the same browser
38  * page, each with their own 'singleton' instances.
39  * <p>
40  * Instance objects are held in a separate Map (keyed by Class) for each
41  * context. For Java, this is just a single static Map. For SwingJS, the map is
42  * stored as a field {@code _swingjsSingletons} of
43  * {@code Thread.currentThread.getThreadGroup()}, as a proxy for the applet.
44  * <p>
45  * Note that when an applet is stopped, its ThreadGroup is removed, allowing any
46  * singleton references to be garbage collected.
47  * 
48  * @author hansonr
49  */
50 public class ApplicationSingletonProvider
51 {
52   /**
53    * A tagging interface to mark classes whose singleton instances may be served
54    * by {@code ApplicationSingletonProvider}, giving a distinct instance per JS
55    * 'applet'.
56    * <p>
57    * A class whose singleton should have global scope (be shared across all
58    * applets on a page) should <em>not</em> use this mechanism, but just provide
59    * a single instance (class static member) in the normal way.
60    */
61   public interface ApplicationSingletonI
62   {
63   }
64   
65   /*
66    * Map used to hold singletons in JVM context
67    */
68   private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> singletons = new HashMap<>();
69
70   /**
71    * private constructor for non-instantiable class
72    */
73   private ApplicationSingletonProvider()
74   {
75   }
76
77   /**
78    * Returns the singletons map for the current context (JVM for Java,
79    * ThreadGroup for JS), creating the map on the first request for each JS
80    * ThreadGroup
81    * 
82    * @return
83    */
84   private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> getContextMap()
85   {
86     @SuppressWarnings("unused")
87     ThreadGroup g = (Platform.isJS()
88             ? Thread.currentThread().getThreadGroup()
89             : null);
90     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = singletons;
91     /** @j2sNative map = g._swingjsSingletons; */
92     if (map == null)
93     {
94       map = new HashMap<>();
95       /** @j2sNative g._swingjsSingletons = map; */
96     }
97
98     return map;
99   }
100
101   /**
102    * Answers the singleton instance of the given class for the current context
103    * (JVM or SwingJS 'applet'). If no instance yet exists, one is created, by
104    * calling the class's no-argument constructor. Answers null if any error
105    * occurs (or occurred previously for the same class).
106    * 
107    * @param c
108    * @return
109    */
110   public static ApplicationSingletonI getInstance(Class<? extends ApplicationSingletonI> c)
111   {
112     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
113     if (map.containsKey(c))
114     {
115       /*
116        * singleton already created _or_ creation failed (null value stored)
117        */
118       return map.get(c);
119     }
120
121     /*
122      * create and save the singleton
123      */
124     ApplicationSingletonI o = map.get(c);
125     try
126     {
127       Constructor<? extends ApplicationSingletonI> con = c
128               .getDeclaredConstructor();
129       con.setAccessible(true);
130       o = con.newInstance();
131     } catch (IllegalAccessException | InstantiationException
132             | IllegalArgumentException | InvocationTargetException
133             | NoSuchMethodException | SecurityException e)
134     {
135       Console.error("Failed to create singleton for " + c.toString()
136               + ", error was: " + e.toString());
137       e.printStackTrace();
138     }
139
140     /*
141      * store the new singleton; note that a
142      * null value is saved if construction failed
143      */
144     getContextMap().put(c, o);
145     return o;
146   }
147
148   /**
149    * Removes the current singleton instance of the given class from the current
150    * application context. This has the effect of ensuring that a new instance is
151    * created the next time one is requested.
152    * 
153    * @param c
154    */
155   public static void removeInstance(
156           Class<? extends ApplicationSingletonI> c)
157   {
158     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
159     if (map != null)
160     {
161       map.remove(c);
162     }
163   }
164 }