/*
* 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
*
* - the Java runtime (JVM) when running as Java
* - one 'applet', when running as JalviewJS
*
* 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 extends ApplicationSingletonI> 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 extends ApplicationSingletonI> 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 extends ApplicationSingletonI> c)
{
Map, ApplicationSingletonI> map = getContextMap();
if (map != null)
{
map.remove(c);
}
}
}