Merge branch 'alpha/origin_2022_JAL-3066_Jalview_212_slivka-integration' into spike...
[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
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 /**
31  * A class to hold singleton objects, whose scope (context) is
32  * <ul>
33  * <li>the Java runtime (JVM) when running as Java</li>
34  * <li>one 'applet', when running as JalviewJS</li>
35  * </ul>
36  * This allows separation of multiple JS applets running on the same browser
37  * page, each with their own 'singleton' instances.
38  * <p>
39  * Instance objects are held in a separate Map (keyed by Class) for each
40  * context. For Java, this is just a single static Map. For SwingJS, the map is
41  * stored as a field {@code _swingjsSingletons} of
42  * {@code Thread.currentThread.getThreadGroup()}, as a proxy for the applet.
43  * <p>
44  * Note that when an applet is stopped, its ThreadGroup is removed, allowing any
45  * singleton references to be garbage collected.
46  * 
47  * @author hansonr
48  */
49 public class ApplicationSingletonProvider
50 {
51   /**
52    * A tagging interface to mark classes whose singleton instances may be served
53    * by {@code ApplicationSingletonProvider}, giving a distinct instance per JS
54    * 'applet'.
55    * <p>
56    * A class whose singleton should have global scope (be shared across all
57    * applets on a page) should <em>not</em> use this mechanism, but just provide
58    * a single instance (class static member) in the normal way.
59    */
60   public interface ApplicationSingletonI
61   {
62   }
63   
64   /*
65    * Map used to hold singletons in JVM context
66    */
67   private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> singletons = new HashMap<>();
68
69   /**
70    * private constructor for non-instantiable class
71    */
72   private ApplicationSingletonProvider()
73   {
74   }
75
76   /**
77    * Returns the singletons map for the current context (JVM for Java,
78    * ThreadGroup for JS), creating the map on the first request for each JS
79    * ThreadGroup
80    * 
81    * @return
82    */
83   private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> getContextMap()
84   {
85     @SuppressWarnings("unused")
86     ThreadGroup g = (Platform.isJS()
87             ? Thread.currentThread().getThreadGroup()
88             : null);
89     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = singletons;
90     /** @j2sNative map = g._swingjsSingletons; */
91     if (map == null)
92     {
93       map = new HashMap<>();
94       /** @j2sNative g._swingjsSingletons = map; */
95     }
96
97     return map;
98   }
99
100   /**
101    * Answers the singleton instance of the given class for the current context
102    * (JVM or SwingJS 'applet'). If no instance yet exists, one is created, by
103    * calling the class's no-argument constructor. Answers null if any error
104    * occurs (or occurred previously for the same class).
105    * 
106    * @param c
107    * @return
108    */
109   public static ApplicationSingletonI getInstance(Class<? extends ApplicationSingletonI> c)
110   {
111     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
112     if (map.containsKey(c))
113     {
114       /*
115        * singleton already created _or_ creation failed (null value stored)
116        */
117       return map.get(c);
118     }
119
120     /*
121      * create and save the singleton
122      */
123     ApplicationSingletonI o = map.get(c);
124     try
125     {
126       Constructor<? extends ApplicationSingletonI> con = c
127               .getDeclaredConstructor();
128       con.setAccessible(true);
129       o = con.newInstance();
130     } catch (IllegalAccessException | InstantiationException
131             | IllegalArgumentException | InvocationTargetException
132             | NoSuchMethodException | SecurityException e)
133     {
134       Cache.log.error("Failed to create singleton for " + c.toString()
135               + ", error was: " + e.toString());
136       e.printStackTrace();
137     }
138
139     /*
140      * store the new singleton; note that a
141      * null value is saved if construction failed
142      */
143     getContextMap().put(c, o);
144     return o;
145   }
146
147   /**
148    * Removes the current singleton instance of the given class from the current
149    * application context. This has the effect of ensuring that a new instance is
150    * created the next time one is requested.
151    * 
152    * @param c
153    */
154   public static void removeInstance(
155           Class<? extends ApplicationSingletonI> c)
156   {
157     Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
158     if (map != null)
159     {
160       map.remove(c);
161     }
162   }
163 }