package com.peterphi.std.util.jaxb;

import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A factory for JAXBSerialiser instances that can optionally be forced to use EclipseLink MOXy (or use the default JAXBContext
 * implementation acquisition rules).<br />
 * Caches JAXBSerialiser instances created using {@link java.lang.ref.SoftReference}s.
 */
public class JAXBSerialiserFactory
{
	private final ConcurrentHashMap<String, SoftReference<JAXBSerialiser>> cache = new ConcurrentHashMap<>();
	private final boolean useMoxy;


	public JAXBSerialiserFactory(boolean useMoxy)
	{
		this.useMoxy = useMoxy;
	}


	public JAXBSerialiser getInstance(Class<?> clazz)
	{
		final String str = clazz.toString();

		final SoftReference<JAXBSerialiser> ref = cache.get(str);
		JAXBSerialiser instance = (ref != null) ? ref.get() : null;

		if (instance == null)
		{
			instance = construct(clazz);
			cache.put(str, new SoftReference<>(instance));
		}

		return instance;
	}


	public JAXBSerialiser getInstance(String contextPath)
	{
		final SoftReference<JAXBSerialiser> ref = cache.get(contextPath);
		JAXBSerialiser instance = (ref != null) ? ref.get() : null;

		if (instance == null)
		{
			instance = construct(contextPath);
			cache.put(contextPath, new SoftReference<>(instance));
		}

		return instance;
	}


	JAXBSerialiser construct(String contextPath)
	{
		if (useMoxy)
			return JAXBSerialiser.getMoxy(contextPath);
		else
			return JAXBSerialiser.getInstance(contextPath);
	}


	JAXBSerialiser construct(Class<?> clazz)
	{
		if (useMoxy)
			return JAXBSerialiser.getMoxy(clazz);
		else
			return JAXBSerialiser.getInstance(clazz);
	}
}

@@ -87,36 +87,121 @@
 til.
-concurrent.ConcurrentHashMap
+Iterator;%0Aimport java.util.Map;%0Aimport java.util.concurrent.ConcurrentHashMap;%0Aimport java.util.function.Supplier
 ;%0A%0A/
@@ -719,37 +719,40 @@
 useMoxy;%0A%09%7D%0A%0A%0A%09p
-ublic
+rotected
  JAXBSerialiser 
@@ -767,66 +767,69 @@
 nce(
-Class%3C?%3E clazz)%0A%09%7B%0A%09%09final String str = clazz.toString();%0A
+final String key, final Supplier%3CJAXBSerialiser%3E provider)%0A%09%7B
 %0A%09%09f
@@ -879,19 +879,19 @@
 che.get(
-str
+key
 );%0A%09%09JAX
@@ -994,23 +994,21 @@
 e = 
-construct(clazz
+provider.get(
 );%0A%09
@@ -1019,19 +1019,19 @@
 che.put(
-str
+key
 , new So
@@ -1060,102 +1060,253 @@
 ));%0A
-%09%09%7D%0A%0A%09%09return instance;%0A%09%7D%0A%0A%0A%09public JAXBSerialiser getInstance(String contextPath)%0A%09%7B%0A%09%09final
+%0A%09%09%09// We just took the penalty to create a JAXBContext, do some maintenance on the map while we're at it%0A%09%09%09prune();%0A%09%09%7D%0A%0A%09%09return instance;%0A%09%7D%0A%0A%0A%09/**%0A%09 * Finds stale entries in the map%0A%09 */%0A%09private void prune()%0A%09%7B%0A%09%09Iterator%3CMap.Entry%3CString,
  Sof
@@ -1323,36 +1323,37 @@
 %3CJAXBSerialiser%3E
- ref
+%3E%3E it
  = cache.get(con
@@ -1349,217 +1349,422 @@
 che.
-get(contextPath);%0A%09%09JAXBSerialiser instance = (ref != null) ? ref
+entrySet().iterator();%0A%0A%09%09while (it.hasNext())%0A%09%09%7B%0A%09%09%09final Map.Entry%3CString, SoftReference%3CJAXBSerialiser%3E%3E entry = it.next();%0A%0A%09%09%09if (entry.getValue() == null %7C%7C entry.getValue()
 .get() 
-:
+==
  null
-;%0A%0A%09%09if (instance == null
+)%0A%09%09%09%09it.remove();%0A%09%09%7D%0A%09%7D%0A%0A%0A%09public JAXBSerialiser getInstance(final Class%3C?%3E clazz
 )%0A
-%09
 %09%7B%0A%09%09
-%09instance = construct(contextPath);%0A%09%09%09cache.put(contextPath, new SoftReference%3C%3E(instance));%0A%09%09%7D%0A
+return getInstance(clazz.toString(), () -%3E construct(clazz));%0A%09%7D%0A%0A%0A%09public JAXBSerialiser getInstance(final String contextPath)%0A%09%7B
 %0A%09%09r
@@ -1765,32 +1765,78 @@
 %09%7B%0A%09%09return 
-i
+getI
 nstance
+(contextPath, () -%3E construct(contextPath))
 ;%0A%09%7D%0A%0A%0A%09JAXB

Update JAXBSerialiserFactory to share more code for for put-if-absent with a Supplier<JAXBSerialiser> to construct. Also add logic to prune empty entries from the map over time