X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=blobdiff_plain;f=src%2Fjalview%2Fbin%2FApplicationSingletonProvider.java;fp=src%2Fjalview%2Fbin%2FApplicationSingletonProvider.java;h=b64f40c6516457aa97b28d261224e29eaf57eb8e;hp=0000000000000000000000000000000000000000;hb=7d602d0e4b439e56af3e4551ed71f181a8025534;hpb=adcef27f5747b4e70e89a56c3735bc3afb8ce9bf diff --git a/src/jalview/bin/ApplicationSingletonProvider.java b/src/jalview/bin/ApplicationSingletonProvider.java new file mode 100644 index 0000000..b64f40c --- /dev/null +++ b/src/jalview/bin/ApplicationSingletonProvider.java @@ -0,0 +1,163 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.bin; + +import jalview.util.Platform; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +/** + * A class to hold singleton objects, whose scope (context) is + * + * This allows separation of multiple JS applets running on the same browser + * page, each with their own 'singleton' instances. + *

+ * Instance objects are held in a separate Map (keyed by Class) for each + * context. For Java, this is just a single static Map. For SwingJS, the map is + * stored as a field {@code _swingjsSingletons} of + * {@code Thread.currentThread.getThreadGroup()}, as a proxy for the applet. + *

+ * Note that when an applet is stopped, its ThreadGroup is removed, allowing any + * singleton references to be garbage collected. + * + * @author hansonr + */ +public class ApplicationSingletonProvider +{ + /** + * A tagging interface to mark classes whose singleton instances may be served + * by {@code ApplicationSingletonProvider}, giving a distinct instance per JS + * 'applet'. + *

+ * A class whose singleton should have global scope (be shared across all + * applets on a page) should not use this mechanism, but just provide + * a single instance (class static member) in the normal way. + */ + public interface ApplicationSingletonI + { + } + + /* + * Map used to hold singletons in JVM context + */ + private static Map, ApplicationSingletonI> singletons = new HashMap<>(); + + /** + * private constructor for non-instantiable class + */ + private ApplicationSingletonProvider() + { + } + + /** + * Returns the singletons map for the current context (JVM for Java, + * ThreadGroup for JS), creating the map on the first request for each JS + * ThreadGroup + * + * @return + */ + private static Map, ApplicationSingletonI> getContextMap() + { + @SuppressWarnings("unused") + ThreadGroup g = (Platform.isJS() + ? Thread.currentThread().getThreadGroup() + : null); + Map, ApplicationSingletonI> map = singletons; + /** @j2sNative map = g._swingjsSingletons; */ + if (map == null) + { + map = new HashMap<>(); + /** @j2sNative g._swingjsSingletons = map; */ + } + + return map; + } + + /** + * Answers the singleton instance of the given class for the current context + * (JVM or SwingJS 'applet'). If no instance yet exists, one is created, by + * calling the class's no-argument constructor. Answers null if any error + * occurs (or occurred previously for the same class). + * + * @param c + * @return + */ + public static ApplicationSingletonI getInstance(Class c) + { + Map, ApplicationSingletonI> map = getContextMap(); + if (map.containsKey(c)) + { + /* + * singleton already created _or_ creation failed (null value stored) + */ + return map.get(c); + } + + /* + * create and save the singleton + */ + ApplicationSingletonI o = map.get(c); + try + { + Constructor con = c + .getDeclaredConstructor(); + con.setAccessible(true); + o = con.newInstance(); + } catch (IllegalAccessException | InstantiationException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) + { + Cache.log.error("Failed to create singleton for " + c.toString() + + ", error was: " + e.toString()); + e.printStackTrace(); + } + + /* + * store the new singleton; note that a + * null value is saved if construction failed + */ + getContextMap().put(c, o); + return o; + } + + /** + * Removes the current singleton instance of the given class from the current + * application context. This has the effect of ensuring that a new instance is + * created the next time one is requested. + * + * @param c + */ + public static void removeInstance( + Class c) + { + Map, ApplicationSingletonI> map = getContextMap(); + if (map != null) + { + map.remove(c); + } + } +}