/* * 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 */ setInstance(c, o); return o; } public static void setInstance(Class c, ApplicationSingletonI o) { getContextMap().put(c, 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); } } }