All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.l4.commons.serialization.AbstractSerializerCollection Maven / Gradle / Ivy

package se.l4.commons.serialization;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import se.l4.commons.serialization.internal.DelayedSerializer;
import se.l4.commons.serialization.internal.TypeEncounterImpl;
import se.l4.commons.serialization.spi.SerializerResolver;
import se.l4.commons.serialization.spi.StaticSerializerResolver;
import se.l4.commons.serialization.spi.Type;
import se.l4.commons.serialization.spi.TypeViaClass;

/**
 * Default implementation of {@link SerializerCollection}.
 *
 * @author Andreas Holstenson
 *
 */
public abstract class AbstractSerializerCollection
	implements SerializerCollection
{
	private static final ThreadLocal> stack = new ThreadLocal>();

	private final Map> nameToSerializer;
	private final Map, QualifiedName> serializerToName;
	private final Map> serializers;

	public AbstractSerializerCollection()
	{
		nameToSerializer = new ConcurrentHashMap>();
		serializerToName = new ConcurrentHashMap, QualifiedName>();
		serializers = new ConcurrentHashMap<>();
	}

	protected  void bind(Class type, Serializer serializer, String ns, String name)
	{
		bind(type, new StaticSerializerResolver(serializer));

		QualifiedName qname = new QualifiedName(ns, name);
		nameToSerializer.put(qname, serializer);
		serializerToName.put(serializer, qname);
	}

	@Override
	public SerializerCollection bind(Class type)
	{
		find(type);

		return this;
	}

	@Override
	public  SerializerCollection bind(Class type, Serializer serializer)
	{
		bind(type, new StaticSerializerResolver(serializer));

		registerIfNamed(type, serializer);

		return this;
	}

	@Override
	@SuppressWarnings("unchecked")
	public  Serializer find(Class type)
	{
		return (Serializer) find(new TypeViaClass(type));
	}

	@Override
	@SuppressWarnings("unchecked")
	public  Serializer find(Class type, Annotation... hints)
	{
		return (Serializer) find(new TypeViaClass(type), hints);
	}

	@Override
	public Serializer find(Type type)
	{
		return find(type, (Annotation[]) null);
	}

	@Override
	@SuppressWarnings({ "rawtypes" })
	public Serializer find(Type type, Annotation... hints)
	{
		Set s = stack.get();
		if(s != null && s.contains(type))
		{
			// Already trying to create this serializer, delay creation
			return new DelayedSerializer(this, type, hints);
		}

		// Locate the resolver to use
		SerializerResolver finder = getResolver(type.getErasedType());
		if(finder == null)
		{
			throw new SerializationException("Unable to retreive serializer for " + type + "; Type does not appear serializable");
		}

		return createVia(finder, type, hints);
	}

	@Override
	public Serializer find(String name)
	{
		return find("", name);
	}

	@Override
	public Serializer find(String namespace, String name)
	{
		return nameToSerializer.get(new QualifiedName(namespace, name));
	}

	@Override
	public  Serializer findVia(Class> resolver, Class type, Annotation... hints)
	{
		return findVia(resolver, new TypeViaClass(type), hints);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public  Serializer findVia(Class> resolver, Type type, Annotation... hints)
	{
		SerializerOrResolver instance = getInstanceFactory().create(resolver);
		if(instance instanceof Serializer)
		{
			return (Serializer) instance;
		}
		else
		{
			return (Serializer) createVia((SerializerResolver) instance, type, hints);
		}
	}

	/**
	 * Create a new {@link Serializer} for the given type and hints via a
	 * specific {@link SerializerResolver resolver} instance.
	 *
	 * @param resolver
	 * @param type
	 * @param hints
	 * @return
	 */
	protected Serializer createVia(SerializerResolver resolver, Type type, Annotation... hints)
	{
		Set s = stack.get();

		// Only expose the hints that the resolver has declared
		Set> hintsUsed = resolver.getHints();
		List hintsActive;
		if(hintsUsed == null || hints == null || hints.length == 0)
		{
			hintsActive = Collections.emptyList();
		}
		else
		{
			hintsActive = new ArrayList<>();
			for(Annotation a : hints)
			{
				if(hintsUsed.contains(a.annotationType()))
				{
					hintsActive.add(a);
				}
			}
		}

		// Check if we have already built a serializer for this type
		CacheKey key = new CacheKey(type, hintsActive.toArray());
		Serializer serializer = serializers.get(key);
		if(serializer != null)
		{
			return serializer;
		}

		// Stack to keep track of circular dependencies
		if(s == null)
		{
			s = new HashSet();
			stack.set(s);
		}

		try
		{
			s.add(type);

			// Find a serializer to use
			TypeEncounterImpl encounter = new TypeEncounterImpl(this, type, hintsActive);

			serializer = resolver.find(encounter);
			if(serializer == null)
			{
				throw new SerializationException("Unable to find serializer for " + type + " using " + resolver.getClass());
			}

			registerIfNamed(type.getErasedType(), serializer);

			// Store the found serializer in the cache
			serializers.put(key, serializer);
			return serializer;
		}
		finally
		{
			s.remove(type);

			if(s.isEmpty())
			{
				stack.remove();
			}
		}
	}

	@Override
	public QualifiedName findName(Serializer serializer)
	{
		return serializerToName.get(serializer);
	}

	@Override
	@SuppressWarnings("rawtypes")
	public boolean isSupported(Class type)
	{
		SerializerResolver finder = getResolver(type);
		if(finder == null) return false;

		if(finder instanceof StaticSerializerResolver)
		{
			return true;
		}

		Serializer serializer = finder.find(new TypeEncounterImpl(this, new TypeViaClass(type), null));
		return serializer != null;
	}

	/**
	 * Register the given serializer if it has a name.
	 *
	 * @param from
	 * @param serializer
	 */
	protected void registerIfNamed(Class from, Serializer serializer)
	{
		if(from.isAnnotationPresent(Named.class))
		{
			Named named = from.getAnnotation(Named.class);
			QualifiedName key = new QualifiedName(named.namespace(), named.name());
			nameToSerializer.put(key, serializer);
			serializerToName.put(serializer, key);
		}
	}

	private static class CacheKey
	{
		private final Type type;
		private final Object[] hints;

		public CacheKey(Type type, Object[] hints)
		{
			this.type = type;
			this.hints = hints;
		}

		@Override
		public int hashCode()
		{
			final int prime = 31;
			int result = 1;
			result = prime * result + Arrays.hashCode(hints);
			result = prime * result + ((type == null) ? 0 : type.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj)
		{
			if(this == obj)
				return true;
			if(obj == null)
				return false;
			if(getClass() != obj.getClass())
				return false;
			CacheKey other = (CacheKey) obj;
			if(!Arrays.equals(hints, other.hints))
				return false;
			if(type == null)
			{
				if(other.type != null)
					return false;
			}
			else if(!type.equals(other.type))
				return false;
			return true;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy