public abstract class Action<K,V,W>
{
	final K key;
	final V value;
	EventType eventType;
	
	// Result
	CacheWriterException writeThroughException = null;
	
	Action(K key, V value, EventType eventType)
	{
		this.key = key;
		this.value = value;
		this.setEventType(eventType);		
	}
	
	
	public void writeThrough(ActionRunner<K,V> actionRunner, Object arg)
	{
		writeThroughImpl(actionRunner, arg);
	}
	
	public void notifyListeners(ActionRunner<K,V> actionRunner, Object arg)
	{
		notifyListenersImpl(actionRunner, arg);
	}
	
	public void statistics(ActionRunner<K,V> actionRunner, Object arg)
	{
		statisticsImpl(actionRunner, arg);
	}

	abstract W writeThroughImpl(ActionRunner<K,V> actionRunner, Object arg);
	abstract void notifyListenersImpl(ActionRunner<K,V> actionRunner, Object arg);
	abstract void statisticsImpl(ActionRunner<K,V> actionRunner, Object arg);
	
	public void close() throws CacheWriterException
	{
		if (writeThroughException != null)
		{
			throw writeThroughException;
		}
	}



	public void setEventType(EventType eventType)
	{
		this.eventType = eventType;
	}


	public boolean successful()
	{
		return writeThroughException == null;
	}


} public abstract class ActionRunner<K,V>
{
	final CacheWriter<K, V> cacheWriter;
	final ListenerCollection<K, V> listeners;
	final StatisticsCalculator stats;

	
	ActionRunner(ActionContext<K,V> actionContext)
	{
		// actionContext. It is currently taken directly from the Cache
	        CacheWriter<K, V> writer = actionContext.cacheWriter();
	        if (writer instanceof NopCacheWriter)
	        {
	            this.cacheWriter = null;
	        }
	        else
	        {               
	            this.cacheWriter = actionContext.cacheWriter();
	        }
		this.listeners = actionContext.listeners();
		this.stats = actionContext.statisticsCalculator();		
	}
	
	public abstract boolean preMutate(Action<K,V,?> action, Object arg);
	public boolean preMutate(Action<K,V,?> action)
	{
		return preMutate(action, null);
	}
	
	public void postMutate(Action<K,V,?> action)
	{
		postMutate(action, null);
	}
	public abstract void postMutate(Action<K,V,?> action, Object arg);
} public class DeleteAction<K,V,W> extends Action<K,V,W>
{
	boolean removed = false;
	
	public DeleteAction(K key)
	{
		super(key, null, EventType.REMOVED);
	}

	@Override
	W writeThroughImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		try
		{
			actionRunner.cacheWriter.delete(key);
		}
		catch (Exception exc)
		{
			writeThroughException = new CacheWriterException(exc);
		}
		return null;
	}

	@Override
	void notifyListenersImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		if (removed)
		{
			@SuppressWarnings("unchecked")
			V oldValue = (V)arg; // This can actually be oldValue or value
			actionRunner.listeners.dispatchEvent(eventType, key, oldValue);
		}
	}

	@Override
	void statisticsImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
//		System.out.println("statisticsImpl removed=" + removed);
		if (removed)
		{
//			System.out.println("statisticsImpl BS removed=" + removed + " stats: " + actionRunner.stats);
			actionRunner.stats.incrementRemoveCount();
//			System.out.println("statisticsImpl AS sremoved=" + removed + " stats: " + actionRunner.stats);
		}
	}

	public void setRemoved(boolean removed)
	{
		this.removed = removed;
	}
} public class DeleteOnValueAction<K,V,W> extends DeleteAction<K,V,W>
{
	final boolean writeThrough;
	
	public DeleteOnValueAction(K key, boolean writeThrough)
	{
		super(key);
		this.writeThrough = writeThrough;
	}

	/**
	 * Cache.remove(key, value) should not write through. There is not even a 2-arg version of CacheWriter.delete(key, value).
	 * Also org.jsr107.tck.integration.CacheWriterTest#shouldWriteThroughRemove_SpecificEntry() enforces that no write-through is being done.
	 * at org.jsr107.tck.integration.CacheWriterTest.shouldWriteThroughRemove_SpecificEntry(CacheWriterTest.java:808)
	 */
	@Override
	W writeThroughImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		if (writeThrough)
			return super.writeThroughImpl(actionRunner, arg);
		else
			return null;
	}
	
	@Override
	void statisticsImpl(ActionRunner<K, V> actionRunner, Object arg)
	{
		// DeleteOnValue is a "delete-if".Thus we need hit counting for the "if value"
		if (removed)
		{
			actionRunner.stats.incrementHitCount();
		}
		else
			actionRunner.stats.incrementMissCount();
		
		super.statisticsImpl(actionRunner, arg);
	}
} public class GetAndPutAction<K, V, W> extends PutAction<K, V, W>
{

	public GetAndPutAction(K key, V value, EventType eventType)
	{
		super(key, value, eventType, true);
	}

	@Override
	void statisticsImpl(ActionRunner<K, V> actionRunner, Object arg)
	{
		StatisticsCalculator stats = actionRunner.stats;
		ChangeStatus changeStatus = (ChangeStatus) arg;
		if (changeStatus == null)
			return;
		
//		System.out.println("actionRunnter " + actionRunner + ", cs=" +changeStatus);
		switch (changeStatus)
		{
			case CHANGED:
				stats.incrementHitCount();
//				stats.incrementPutCount(); // cache.putToMap() still does it. Thus do not increment here
				break;
			case CREATED:
				stats.incrementMissCount();
//				stats.incrementPutCount(); // cache.putToMap() still does it. Thus do not increment here
				break;
			case UNCHANGED:
			case CAS_FAILED_EQUALS:
				break;
		

		}
	}
} public class GetAndRemoveAction<K,V,W> extends DeleteAction<K, V, W>
{
	public GetAndRemoveAction(K key)
	{
		super(key);
	}

	@Override
	void statisticsImpl(ActionRunner<K,V> actionRunner, Object oldValue)
	{
		if (removed)
		{
			actionRunner.stats.incrementHitCount();
			super.statisticsImpl(actionRunner, oldValue);
		}
		else
		{
			actionRunner.stats.incrementMissCount();
		}
	}
} public class PutAction<K,V,W> extends Action<K,V,W>
{

	final boolean countStatistics;
	final boolean writeThrough;
	public PutAction(K key, V value, EventType eventType, boolean countStatistics)
	{
		super(key, value, eventType);
		this.countStatistics = countStatistics;
		this.writeThrough = true;
	}

	public PutAction(K key, V value, EventType eventType, boolean countStatistics, boolean writeThrough)
	{
		super(key, value, eventType);
		this.countStatistics = countStatistics;
		this.writeThrough = writeThrough;
	}

	@Override
	W writeThroughImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		if (!writeThrough)
			return null;
		
		try
		{
			actionRunner.cacheWriter.write(new TCacheJSR107Entry<K,V>(key, value));
		}
		catch (Exception exc)
		{
			writeThroughException = new CacheWriterException(exc);
		}
		return null;
	}

	@Override
	void notifyListenersImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		if (eventType != null)
			actionRunner.listeners.dispatchEvent(eventType, key, value);
	}

	@Override
	void statisticsImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		if (countStatistics)
			actionRunner.stats.incrementPutCount();
	}

} public class ReplaceAction<K, V, W> extends PutAction<K, V, W>
{

	public ReplaceAction(K key, V value, EventType eventType)
	{
		super(key, value, eventType, true);
	}

	@Override
	W writeThroughImpl(ActionRunner<K,V> actionRunner, Object arg)
	{
		ChangeStatus changeStatus = (ChangeStatus) arg;
		if (changeStatus == ChangeStatus.CHANGED)
			return super.writeThroughImpl(actionRunner, arg);
		
		return null;
	}
	
	@Override
	void statisticsImpl(ActionRunner<K, V> actionRunner, Object arg)
	{
		StatisticsCalculator stats = actionRunner.stats;
		ChangeStatus changeStatus = (ChangeStatus) arg;
		switch (changeStatus)
		{
			case CHANGED:
				stats.incrementHitCount();
				stats.incrementPutCount();
				break;
			case UNCHANGED:
				stats.incrementMissCount();
				break;
			case CREATED:
//				System.out.println("actionRunnter " + actionRunner + ", cs=" +changeStatus + ", stats B:" + stats);
				stats.incrementMissCount();
//				stats.incrementPutCount(); // cache.putToMap() still does it. Thus do not increment here
//				System.out.println("actionRunnter " + actionRunner + ", cs=" +changeStatus + ", stats A:" + stats);
				break;
			case CAS_FAILED_EQUALS:
				stats.incrementHitCount();
				break;
		}
	}
} public class WriteBehindActionRunner<K,V> extends ActionRunner<K,V>
{
	public WriteBehindActionRunner(ActionContext<K,V> actionContext)
	{
		super(actionContext);
	}
	
	@Override
	public boolean preMutate(Action<K,V,?> action, Object arg)
	{
		return true;
	}

	@Override
	public void postMutate(Action<K, V, ?> action, Object arg)
	{
		action.statistics(this, arg);
		action.notifyListeners(this, arg);
		if (cacheWriter != null)
			action.writeThrough(this, arg); // Should run async and possibly batched for efficient write-behind
		action.close();
		
	}
} public class WriteThroughActionRunner<K,V> extends ActionRunner<K,V>
{
	public WriteThroughActionRunner(ActionContext<K,V> actionContext)
	{
		super(actionContext);
	}
	
	@Override
	public boolean preMutate(Action<K,V,?> action, Object arg)
	{
		if (cacheWriter != null)
			action.writeThrough(this, arg);
		return action.successful();
	}

	@Override
	public void postMutate(Action<K, V, ?> action, Object arg)
	{
		action.statistics(this, arg);
		action.notifyListeners(this, arg);
		action.close();
	}

} public class Builder<K,V>  implements TriavaCacheConfiguration<K, V, Builder<K,V>>
{
	private static final long serialVersionUID = -4430382287782891844L;

	static final AtomicInteger anonymousCacheId = new AtomicInteger();

	private String id;
	private boolean strictJSR107 = true;
	private long maxCacheTime = 3600_000; // In MILLISECONDS. 60 minutes
	private long maxCacheTimeSpread = 0; // In MILLISECONDS. 0 seconds
	private int expectedMapSize = 10000;
	private int concurrencyLevel = 14;
	private int mapConcurrencyLevel = 16;
	private long cleanUpIntervalMillis = 0; // 0 = auto-tuning

	private EvictionPolicy evictionPolicy = EvictionPolicy.LFU;
	private EvictionInterface<K, V> evictionClass = null;
	private HashImplementation hashImplementation = HashImplementation.ConcurrentHashMap;
	private JamPolicy jamPolicy = JamPolicy.WAIT;
	private boolean statistics = false; // off by JSR107 default
	private boolean management = false; // off by JSR107 default
	private CacheWriteMode writeMode = CacheWriteMode.Identity;
	private Class<K> keyType = objectKeyType();
	private Class<V> valueType = objectValueType();
	
	private Collection<CacheEntryListenerConfiguration<K, V>> listenerConfigurations = new ArrayList<>(0);
	private Factory<CacheWriter<? super K, ? super V>> writerFactory = null;
	private Factory<ExpiryPolicy> expiryPolicyFactory = EternalExpiryPolicy.factoryOf();

	private CacheLoader<K, V> loader = null;
	private Factory<javax.cache.integration.CacheLoader<K, V>> loaderFactory = null;
	
	private boolean writeThrough = false;
	private boolean readThrough = false;

	/**
	 * Native Builder for creating Cache instances. The returned object is initialized with default values.
	 * The native Builder by default uses a STORE_BY_REFERENCE model instead of the JSR107 default of STORE_BY_VALUE. 
	 * <p>
	 * Any Cache created by {@link #build()} is registered in the given factory/CacheManager. 
	 */
	public Builder()
	{
		management = true;
		statistics = true;
		strictJSR107 = false;
	}

	/**
	 * A Builder that is target for usage in JSR107 scenarios.
	 * It takes a JSR107 Configuration object to define defaults, and its isStoreByValue() value
	 * is converted to a CacheWriteMode using {@link CacheWriteMode#fromStoreByValue(boolean)}.
	 * <p>
	 * Defaults for this Builder are taken from the given
	 * configuration, which can be a plain JSR107 Configuration or a Builder itself. The
	 * given configuration is copied in both cases, so subsequent changes to the original object will have
	 * no effect on this Builder or any Cache created from it.
	 * <p>
	 * Any Cache created by {@link #build()} is registered in the given factory/CacheManager. 
	 * 
	 * @param configuration Cache Configuration, which can also be a Builder
	 */
	public Builder(Configuration<K,V> configuration)
	{
		this.writeMode = null; // null means to derive it from configuration.isStoreByValue()
		this.maxCacheTime = Long.MAX_VALUE; // JSR107 operates on ExpiryPolicy only
		copyBuilder(configuration, this);
	}


	/**
	 * Builds a Cache from the parameters that were set. evictionType defines the eviction policy. Any not
	 * explicitly set parameters will get a default values, which are:
	 * 
	 * <pre>
	 * private long maxIdleTime = 1800;
	 * private long maxCacheTime = 3600;
	 * private int expectedMapSize = 10000;
	 * private int concurrencyLevel = 16;
	 * private TCacheEvictionType evictionType = TCacheEvictionType.LFU;
	 * 
	 * </pre>
	 * 
	 * Any Cache created by {@link #build()} is registered in the associated factory/CacheManager. 
	 * 
	 * @return The Cache
	 */
	public Cache<K, V> build()
	{
		throw new UnsupportedOperationException("build() is only supported by internal subclasses.");
	}
	
	static <Arg> Arg verifyNotNull(String name, Arg arg)
	{
		if (arg == null)
		{
			throw new IllegalArgumentException(name + " is null");
		}
		return arg;
	}


	@Override
	public Builder<K,V> setId(String id)
	{
		this.id = id;
		return this;
	}

	@Override
	public Builder<K,V> setMaxIdleTime(int maxIdleTime, TimeUnit timeUnit)
	{
		if (maxIdleTime <= 0)
			throw new IllegalArgumentException("Invalid maxIdleTime: " + maxIdleTime);
		
		TouchedExpiryPolicy ep = new TouchedExpiryPolicy(new Duration(timeUnit, maxIdleTime));
		SingletonFactory<ExpiryPolicy> singletonFactory = new FactoryBuilder.SingletonFactory<ExpiryPolicy>(ep);
		this.expiryPolicyFactory = (Factory<ExpiryPolicy>)singletonFactory;
		
		return this;
	}

	@Override
	public Builder<K,V> setMaxCacheTime(int maxCacheTime, int interval, TimeUnit timeUnit)
	{
		if (interval <= 0)
			throw new IllegalArgumentException("Invalid interval: " + interval);
		setMaxCacheTime(maxCacheTime, timeUnit);
		this.maxCacheTimeSpread = timeUnit.toMillis(interval);
		return this;
	}

	@Override
	public Builder<K, V> setMaxCacheTime(int maxCacheTime, TimeUnit timeUnit)
	{
		if (maxCacheTime <= 0)
			throw new IllegalArgumentException("Invalid maxCacheTime: " + maxCacheTime);
		this.maxCacheTime = timeUnit.toMillis(maxCacheTime);
		return this;
	}

	@Override
	public Builder<K, V> setCleanupInterval(int cleanupInterval, TimeUnit timeUnit)
	{
		this.cleanUpIntervalMillis = timeUnit.toMillis(cleanupInterval);
		return this;
	}

	/**
	 * @deprecated Use {@link #setMaxElements(int)}
	 * @param maxElements See {@link #setMaxElements(int)}
	 * @return This Builder
	 */
	public Builder<K,V> setExpectedMapSize(int maxElements)
	{
		return setMaxElements(maxElements);
	}
	
	@Override
	public Builder<K,V> setMaxElements(int maxElements)
	{
		if (maxElements < 0)
			throw new IllegalArgumentException("Invalid expectedMapSize: " + expectedMapSize);
		this.expectedMapSize = maxElements;
		return this;
	}

	@Override
	public Builder<K,V> setConcurrencyLevel(int concurrencyLevel)
	{
		this.concurrencyLevel = concurrencyLevel;
		this.mapConcurrencyLevel = Math.max(concurrencyLevel + 2, 8);
		return this;
	}

	@Override
	public Builder<K,V> setEvictionPolicy(EvictionPolicy evictionPolicy)
	{
		this.evictionPolicy = evictionPolicy;
		this.evictionClass = null;
		return this;
	}

	@Override
	public Builder<K,V> setEvictionClass(EvictionInterface<K, V> clazz)
	{
	    // TODO This is also called for standard LFU and LRU caches, so ANY cache currently has  evictionPolicy == CUSTOM after construction.
		this.evictionPolicy = EvictionPolicy.CUSTOM;
		this.evictionClass = clazz;
		return this;
	}

	/**
	 * @return the evictionClass. null if there is no custom class
	 */
	public EvictionInterface<K, V> getEvictionClass()
	{
		return evictionClass;
	}


	/**
	 * Set the StorageBackend for the underlying ConcurrentMap. If this method is not called,
	 * ConcurrentHashMap will be used.
	 *  
	 * @param hashImplementation The {@link HashImplementation}
	 * @return This Builder
	 * 
	 */
	@Beta(comment="To be replaced by a method to set a StorageBackend")
	public Builder<K,V> setHashImplementation(HashImplementation hashImplementation)
	{
		this.hashImplementation = hashImplementation;
		return this;
	}

	/**
	 * Sets the policy, how a Thread that calls put() will behave the cache is full.
	 * Either the Thread will WAIT or DROP the element and not put it in the cache.
	 * The default is WAIT. The {@link JamPolicy} has no effect on caches of unlimited size
	 * {@link EvictionPolicy}}.NONE.
	 * 
	 * @param jamPolicy The {@link JamPolicy}
	 * @return This Builder
	 */
	public Builder<K,V> setJamPolicy(JamPolicy jamPolicy)
	{
		this.jamPolicy = jamPolicy;
		return this;
	}

//	/**
//	 * @return the factory
//	 */
//	public TCacheFactory getFactory()
//	{
//		return factory;
//	}

	public boolean getStatistics()
	{
		return statistics;
	}

	/**
	 * Sets whether statistics should be gathered. The performance impact on creating statistics is very low,
	 * so it is safe to activate statistics. If this method is not called, the default is to collect statistics.
	 * 
	 * @param statistics true, if you want to switch on statistics 
	 * @return This Builder
	 */
	public Builder<K,V> setStatistics(boolean statistics)
	{
		this.statistics = statistics;
		return this;
	}

	/**
	 * Sets whether management should be enabled.
	 * 
	 * @param management true, if you want to switch on management 
	 * @return This Builder
	 */
	public Builder<K,V> setManagement(boolean management)
	{
		this.management = management;
		return this;
	}

	/**
	 * @return the id
	 */
	public String getId()
	{
		return id;
	}

	/**
	 * @return the maxIdleTime in milliseconds.
	 */
	public long getMaxIdleTime()
	{
		Duration accessExpiry = expiryPolicyFactory.create().getExpiryForAccess();
		return accessExpiry.getAdjustedTime(0);
	}

	/**
	 * @return The lower bound of the "maximum cache time interval" [maxCacheTime, maxCacheTime+maxCacheTimeSpread] in milliseconds.
	 */
	public long getMaxCacheTime()
	{
		return maxCacheTime;
	}

	/**
	 * 
	 * @return The interval size of the cache time interval in milliseconds.
	 */
	public long getMaxCacheTimeSpread()
	{
		return maxCacheTimeSpread;
	}

	/**
	 * Returns the proposed cleanup interval in ms. See {@link #setCleanupInterval(int, TimeUnit)} for details on how the Cache uses this value.   
	 * @return The proposed cleanup interval in ms. 0 means auto-tuning
	 */
	public long getCleanUpIntervalMillis()
	{
		return cleanUpIntervalMillis;
	}

	@Override
	public int getMaxElements()
	{
		return expectedMapSize;
	}

	/**
	 * @return the concurrencyLevel
	 */
	public int getConcurrencyLevel()
	{
		return concurrencyLevel;
	}

	public int getMapConcurrencyLevel()
	{
		return mapConcurrencyLevel;
	}

	/**
	 * @return the evictionPolicy
	 */
	public EvictionPolicy getEvictionPolicy()
	{
		return evictionPolicy;
	}

	/**
	 * @return the hashImplementation
	 */
	public HashImplementation getHashImplementation()
	{
		return hashImplementation;
	}

	public StorageBackend<K, V> storageFactory()
	{
		switch (hashImplementation)
		{
			case ConcurrentHashMap:
				return new JavaConcurrentHashMap<K, V>();
//			case PerfTestGuavaLocalCache:
//				return new GuavaLocalCache<K, V>();
			case HighscalelibNonBlockingHashMap:
				return new HighscalelibNonBlockingHashMap<K, V>();
			default:
				return null;
		}
	}

	public JamPolicy getJamPolicy()
	{
		return jamPolicy;
	}

	public CacheLoader<K, V> getLoader()
	{
		return (CacheLoader<K, V>) loader;
	}

	public Builder<K, V> setLoader(CacheLoader<K, V> loader)
	{
		this.loader = loader;
		return this;
	}

	@Override
	public Factory<javax.cache.integration.CacheLoader<K, V>> getCacheLoaderFactory()
	{
		return loaderFactory;
	}


	@SuppressWarnings("unchecked")
	@Override // JSR107
	public Class<K> getKeyType()
	{
		return keyType == null ? (Class<K>)Object.class : keyType;
	}

	@Override // JSR107
	public Class<V> getValueType()
	{
		return valueType;
	}

	public void setKeyType(Class<K> keyType)
	{
		this.keyType = keyType;
	}

	public void setValueType(Class<V> valueType)
	{
		this.valueType = valueType;
	}

	@Override // JSR107
	public boolean isStoreByValue()
	{
		
		return writeMode.isStoreByValue();
	}
	
	public CacheWriteMode getCacheWriteMode()
	{
		return writeMode;
	}

	public Builder<K, V> setCacheWriteMode(CacheWriteMode writeMode)
	{
		this.writeMode = writeMode;
		return this;
	}

	/**
	 * Returns a representation of the Configuration as Properties.
	 * The returned properties are a private copy for the caller and thus not shared amongst different callers.
	 * Changes to the returned Properties have no effect on the Cache.
	 * 
	 * @param propsType If PropsType.CacheManager, the Cache specific properties (cacheName, cacheLoaderClass) are excluded
	 * @return The current configuration
	 */
	@Override
	public Properties asProperties(PropsType propsType)
	{
		boolean propsForCache = propsType == PropsType.Cache;
		Properties props = new Properties();
		
		if (propsForCache)
			props.setProperty("cacheName", id);
		props.setProperty("maxIdleTime", Long.toString(getMaxIdleTime()));
		props.setProperty("maxCacheTime", Long.toString(maxCacheTime));
		props.setProperty("maxCacheTimeSpread", Long.toString(maxCacheTimeSpread));
		props.setProperty("expectedMapSize", Integer.toString(expectedMapSize));
		props.setProperty("concurrencyLevel", Integer.toString(concurrencyLevel));
		props.setProperty("evictionPolicy", evictionPolicy.toString());
		props.setProperty("hashMapClass", hashImplementation.toString());
		props.setProperty("jamPolicy", jamPolicy.toString());
		props.setProperty("statistics", Boolean.toString(statistics));
		if (propsForCache)
			props.setProperty("cacheLoaderClass", loader == null ? "null" : loader.getClass().getName());
		props.setProperty("writeMode", writeMode.toString());
		
		return props;
	}

	/**
	 * Copies the configuration to the target Builder. If the source (configuration)
	 * is also a Builder, its fields also get copied. Any null-value in the
	 * configuration is ignored in the copying process, leaving the corresponding
	 * target value unchanged. The CacheWriteMode can be
	 * defined in two ways: If configuration is a Builder it is copied plainly,
	 * otherwise it is derived from configuration.isStoreByValue().
	 * 
	 * @param configuration The source builder
	 * @param target The target builder
	 */
	private void copyBuilder(Configuration<K, V> configuration, Builder<K, V> target)
	{
		CacheWriteMode tcacheWriteMode = null;
		if (configuration instanceof Builder)
		{
			Builder<K, V> sourceB = (Builder<K, V>)configuration;
			// tCache native configuration
			if (sourceB.id != null)
				target.id = sourceB.id;
			target.strictJSR107 = sourceB.strictJSR107;
			target.maxCacheTime = sourceB.maxCacheTime;
			target.maxCacheTimeSpread = sourceB.maxCacheTimeSpread;
			this.cleanUpIntervalMillis = sourceB.cleanUpIntervalMillis;
			target.expectedMapSize = sourceB.expectedMapSize;
			target.concurrencyLevel = sourceB.concurrencyLevel;
			if (sourceB.evictionPolicy != null)
				target.evictionPolicy = sourceB.evictionPolicy;
			if (sourceB.evictionClass != null)
				target.evictionClass = sourceB.evictionClass;			
			if (sourceB.hashImplementation != null)
				target.hashImplementation = sourceB.hashImplementation;
			if (sourceB.jamPolicy != null)
				target.jamPolicy = sourceB.jamPolicy;
			if (sourceB.loader != null)
				target.loader = sourceB.loader; // loader vs loaderFactory

			tcacheWriteMode = sourceB.writeMode;
		}
		
		if (configuration instanceof CompleteConfiguration)
		{
			CompleteConfiguration<K,V> cc = (CompleteConfiguration<K,V>)configuration;
			target.statistics = cc.isStatisticsEnabled();
			target.management = cc.isManagementEnabled();
			
			target.expiryPolicyFactory = cc.getExpiryPolicyFactory();
			target.writerFactory = cc.getCacheWriterFactory();
			Factory<javax.cache.integration.CacheLoader<K, V>> lf = cc.getCacheLoaderFactory();
			if (lf != null)
			{
				target.loader = null; // loader vs loaderFactory
				target.loaderFactory = lf;
			}
			
			Collection<CacheEntryListenerConfiguration<K, V>> listenerConfsCopy = new ArrayList<>(0);
			for (CacheEntryListenerConfiguration<K, V> entry : cc.getCacheEntryListenerConfigurations())
			{
				listenerConfsCopy.add(entry);
			}
			target.listenerConfigurations = listenerConfsCopy;
			
			target.writeThrough = cc.isWriteThrough();
			target.readThrough =  cc.isReadThrough();
		}
		
		// JSR107 configuration follows
		if (tcacheWriteMode != null)
			target.writeMode = tcacheWriteMode;
		else
			target.writeMode = CacheWriteMode.fromStoreByValue(configuration.isStoreByValue());
		
		target.keyType = configuration.getKeyType();
		target.valueType = configuration.getValueType();

	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + concurrencyLevel;
		result = prime * result + ((evictionClass == null) ? 0 : evictionClass.hashCode());
		result = prime * result + ((evictionPolicy == null) ? 0 : evictionPolicy.hashCode());
		result = prime * result + expectedMapSize;
		result = prime * result + ((hashImplementation == null) ? 0 : hashImplementation.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((jamPolicy == null) ? 0 : jamPolicy.hashCode());
		result = prime * result + ((keyType == null) ? 0 : keyType.hashCode());
		result = prime * result + ((loader == null) ? 0 : loader.hashCode());
		result = prime * result + mapConcurrencyLevel;
		result = prime * result + (int) (maxCacheTime ^ (maxCacheTime >>> 32));
		result = prime * result + (int) (maxCacheTimeSpread ^ (maxCacheTimeSpread >>> 32));
		result = prime * result + (int) (cleanUpIntervalMillis ^ (cleanUpIntervalMillis >>> 32));
		result = prime * result + expiryPolicyFactory.hashCode();
		result = prime * result + (statistics ? 1231 : 1237);
		result = prime * result + ((valueType == null) ? 0 : valueType.hashCode());
		result = prime * result + ((writeMode == null) ? 0 : writeMode.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;
		if (! (obj instanceof Builder))
			return false;

		Builder<?,?> other = (Builder<?,?>) obj;
		if (concurrencyLevel != other.concurrencyLevel)
			return false;
		if (evictionClass == null)
		{
			if (other.evictionClass != null)
				return false;
		}
		else if (!evictionClass.equals(other.evictionClass))
			return false;
		if (evictionPolicy != other.evictionPolicy)
			return false;
		if (expectedMapSize != other.expectedMapSize)
			return false;
		if (hashImplementation != other.hashImplementation)
			return false;
		if (id == null)
		{
			if (other.id != null)
				return false;
		}
		else if (!id.equals(other.id))
			return false;
		if (jamPolicy != other.jamPolicy)
			return false;
		if (keyType == null)
		{
			if (other.keyType != null)
				return false;
		}
		else if (!keyType.equals(other.keyType))
			return false;
		if (loader == null)
		{
			if (other.loader != null)
				return false;
		}
		else if (!loader.equals(other.loader))
			return false;
		if (mapConcurrencyLevel != other.mapConcurrencyLevel)
			return false;
		if (maxCacheTime != other.maxCacheTime)
			return false;
		if (maxCacheTimeSpread != other.maxCacheTimeSpread)
			return false;
		if (cleanUpIntervalMillis != other.cleanUpIntervalMillis)
			return false;
		if (! expiryPolicyFactory.equals(other.expiryPolicyFactory))
			return false;
		if (statistics != other.statistics)
			return false;
		if (valueType == null)
		{
			if (other.valueType != null)
				return false;
		}
		else if (!valueType.equals(other.valueType))
			return false;
		if (writeMode != other.writeMode)
			return false;
		return true;
	}

	@Override
	public boolean isReadThrough()
	{
		return readThrough;
	}

	
	public Builder<K, V> setCacheLoaderFactory(Factory<javax.cache.integration.CacheLoader<K, V>> loaderFactory)
	{
		this.loaderFactory = loaderFactory;
		return this;
	}

	@SuppressWarnings("unchecked")
	public Builder<K, V> setCacheWriterFactory(Factory<? extends CacheWriter<? super K, ? super V>> factory)
	{
		this.writerFactory = (Factory<CacheWriter<? super K, ? super V>>) factory;
		return this;
	}
	
	public Builder<K, V> setReadThrough(boolean isReadThrough)
	{
		this.readThrough = isReadThrough;
		return this;
	}

	public Builder<K, V> setWriteThrough(boolean isWriteThrough)
	{
		this.writeThrough = isWriteThrough;
		return this;
	}

	@Override
	public boolean isWriteThrough()
	{
		return writeThrough;
	}

	@Override
	public boolean isStatisticsEnabled()
	{
		return statistics;
	}

	@Override
	public boolean isManagementEnabled()
	{
		return management;
	}
	/**
	 * Returns whether the Cache behaves strictly JSR107 compliant
	 * 
	 * @return true if the Cache behaves strictly JSR107 compliant
	 */
	public boolean isStrictJSR107()
	{
		return strictJSR107;
	}

	public void setStrictJSR107(boolean strictJSR107)
	{
		this.strictJSR107 = strictJSR107;
	}

	@Override
	public Iterable<CacheEntryListenerConfiguration<K, V>> getCacheEntryListenerConfigurations()
	{
		return listenerConfigurations;
	}

	@Override
	public Factory<CacheWriter<? super K, ? super V>> getCacheWriterFactory()
	{
		return writerFactory;
	}

	@Override
	public Factory<ExpiryPolicy> getExpiryPolicyFactory()
	{
		return expiryPolicyFactory;
	}

	/**
	 * Sets the ExpiryPolicyFactory. It will overwrite any values set before via {@link #setMaxIdleTime(int, TimeUnit)}.
	 * @param factory The factory
	 * @return This Builder
	 */
	public Builder<K,V> setExpiryPolicyFactory(Factory<? extends ExpiryPolicy> factory)
	{
		if (expiryPolicyFactory == null)
		{
			this.expiryPolicyFactory = EternalExpiryPolicy.factoryOf();
		}
		else
		{
			@SuppressWarnings("unchecked")
			Factory<ExpiryPolicy> factoryCasted = (Factory<ExpiryPolicy>) factory;
			this.expiryPolicyFactory = (factoryCasted);
		}
		
		return this;
	}	

	/**
	 * Return Object.class casted suitably for {@link #getValueType()}
	 * @return Object.class
	 */
	@SuppressWarnings("unchecked")
	private Class<V> objectValueType()
	{
		return (Class<V>)Object.class;
	}

	/**
	 * Return Object.class casted suitably for {@link #getKeyType()}
	 * @return Object.class
	 */
	@SuppressWarnings("unchecked")
	private Class<K> objectKeyType()
	{
		return (Class<K>)Object.class;
	}

	public void addCacheEntryListenerConfiguration(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		listenerConfigurations.add(listenerConfiguration);
	}

	public void removeCacheEntryListenerConfiguration(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		listenerConfigurations.remove(listenerConfiguration);
	}

} public class Holders<V>
{
	public final AccessTimeObjectHolder<V> newHolder;
	public final AccessTimeObjectHolder<V> oldHolder;
	public final AccessTimeObjectHolder<V> effectiveHolder;
	
	public Holders(AccessTimeObjectHolder<V> newHolder,  AccessTimeObjectHolder<V> oldHolder, AccessTimeObjectHolder<V> effectiveHolder)
	{
		this.newHolder = newHolder;
		this.oldHolder = oldHolder;
		this.effectiveHolder = effectiveHolder;
	}
} public class NopCacheWriter<K,V> implements CacheWriter<K, V>
{
	@Override
	public void write(Cache.Entry<? extends K, ? extends V> entry) throws CacheWriterException
	{
	}

	@Override
	public void writeAll(Collection<Cache.Entry<? extends K, ? extends V>> entries) throws CacheWriterException
	{
	}

	@Override
	public void delete(Object key) throws CacheWriterException
	{
	}

	@Override
	public void deleteAll(Collection<?> keys) throws CacheWriterException
	{
	}

} public class TCacheConfigurationBean<K,V> implements CacheMXBean
{
	final Cache<K, V> tcache;
	
	public TCacheConfigurationBean(Cache<K, V> tcache)
	{
		this.tcache = tcache;
	}

	@Override
	public String getKeyType()
	{
		Configuration<K,V> config = tcache.configuration();
		return config.getKeyType().getCanonicalName();
	}

	@Override
	public String getValueType()
	{
		Configuration<K,V> config = tcache.configuration();
		return config.getValueType().getCanonicalName();
	}

	@Override
	public boolean isReadThrough()
	{
		return tcache.configuration().isReadThrough();
	}

	@Override
	public boolean isWriteThrough()
	{
		return tcache.configuration().isWriteThrough();
	}

	@Override
	public boolean isStoreByValue()
	{
		return tcache.configuration().isStoreByValue();
	}

	@Override
	public boolean isStatisticsEnabled()
	{
		return tcache.isStatisticsEnabled();
	}

	@Override
	public boolean isManagementEnabled()
	{
		return tcache.isManagementEnabled();
	}
} public class TCacheEntryIterator<K, V> implements Iterator<Entry<K,V>>
{
	private final Iterator<java.util.Map.Entry<K, AccessTimeObjectHolder<V>>> mapIterator;
	Entry<K, V> currentElement = null;
	java.util.Map.Entry<K, AccessTimeObjectHolder<V>> nextElement = null;
	final Cache<K,V> cache;
	final StatisticsCalculator statisticsCalculator;
	final TCacheExpiryPolicy expiryPolicy;
	
	public TCacheEntryIterator(com.trivago.triava.tcache.Cache<K, V> tcache, ConcurrentMap<K, AccessTimeObjectHolder<V>> objects, TCacheExpiryPolicy expiryPolicy)
	{
		this.expiryPolicy = expiryPolicy;
		mapIterator = objects.entrySet().iterator();

		this.statisticsCalculator = tcache.statisticsCalculator();
		this.cache = tcache.jsr107cache();
	}

	@Override
	public boolean hasNext()
	{
		java.util.Map.Entry<K, AccessTimeObjectHolder<V>> entry = peekNext();
		if (entry == null)
			return false;
		return ! entry.getValue().isInvalid();
	}

	@Override
	public Entry<K, V> next()
	{
		try
		{
			java.util.Map.Entry<K, AccessTimeObjectHolder<V>> entry = peekNext();
			nextElement = null;
			if (entry == null)
				throw new NoSuchElementException();

			AccessTimeObjectHolder<V> holder = entry.getValue();
			holder.updateMaxIdleTime(expiryPolicy.getExpiryForAccess());
			V value = holder.get();
			// To be considered: value could be null now, if it got invalid between the peekNext() and peek() calls
			// This means entry.value can be null. This is likely not a good design decision. Future directions: Rethink this
			currentElement = new TCacheJSR107Entry<K, V>(entry.getKey(), value);
			if (statisticsCalculator != null)
				statisticsCalculator.incrementHitCount();
			return currentElement;
			
		}
		catch (NoSuchElementException nsee)
		{
			currentElement = null;
			throw nsee;
		}
	}

	/**
	 * Peeks the next element. Calling this method multiple times will always return
	 * the same value, until {@link #next()} is called. This method skips all invalid
	 * Holders.   
	 * 
	 * @return The next element, or null if there is no nbext element
	 */
	private java.util.Map.Entry<K, AccessTimeObjectHolder<V>> peekNext()
	{
		if (nextElement != null)
			return nextElement;
		
		while (true)
		{
			if (!mapIterator.hasNext())
				return null;
			java.util.Map.Entry<K, AccessTimeObjectHolder<V>> entry = mapIterator.next();
			AccessTimeObjectHolder<V> holder = entry.getValue();
			if (!holder.isInvalid())
			{
				nextElement = entry;
				return nextElement;
			}
		}
	}
	
	@Override
	public void remove()
	{
		if (currentElement == null)
			throw new IllegalStateException("No element to remove");
		cache.remove(currentElement.getKey());
		// Done: Actions like statistics and write-through are done by cache.remove()
	}
} public class TCacheJSR107Entry<K, V> implements javax.cache.Cache.Entry<K, V>
{
	final K key;
	final V value;
	
	/**
	 * Creates an instance based on the native tCache entry plus the key.
	 * 
	 * @param key The key
	 * @param value The value
	 */
	public TCacheJSR107Entry(K key, V value)
	{
		this.key = key;
		this.value = value;
	}

	@Override
	public K getKey()
	{
		return key;
	}

	@Override
	public V getValue()
	{
		return value;
	}

	@Override
	public <T> T unwrap(Class<T> clazz)
	{
		if (clazz.isAssignableFrom(TCacheJSR107Entry.class))
		{
			@SuppressWarnings("unchecked")
			T holderCast = (T) this;
			return holderCast;
		}

		throw new IllegalArgumentException("Cannot unwrap to unsupported Class " + clazz);
	}
} public final class TCacheJSR107MutableEntry<K, V> extends TCacheJSR107Entry<K, V> implements MutableEntry<K, V>
{
	public enum Operation { NOP, REMOVE, SET, LOAD, REMOVE_WRITE_THROUGH, GET }
	private Operation operation = Operation.NOP;
	V valueNew = null;
	final CacheLoader<K, V> loader;
	
	/**
	 * Creates an instance based on the native tCache entry plus the key.
	 * 
	 * @param key The key
	 * @param value The value
	 * @param loader The CacheLoader, or null if there is no CacheLoader
	 */
	public TCacheJSR107MutableEntry(K key, V value, CacheLoader<K, V> loader)
	{
		super(key, value);
		this.valueNew = value;
		this.loader = loader;
	}

	@Override
	public boolean exists()
	{
		return valueNew != null;
	}

	@Override
	public void remove()
	{
		if (operation == Operation.SET || operation == Operation.LOAD)
		{
			operation = Operation.NOP;
		}
		else
		{
			if (oldValue() != null)
				operation = Operation.REMOVE;
			else
			{
				// TCK Challenge
				// CacheWriterTest.shouldWriteThroughUsingInvoke_setValue_CreateEntryThenRemove()
				// It demands that no action (INCLUDING write-through) is done, if the State-transistion is:
				// SURROGATE -> SET -> REMOVED     : Result NOP
				// SURROGATE -> REMOVED            : Result REMOVE_WRITE_THROUGH
				// I do not see why we should skip write-through in this case
				if (valueNew != null)
					operation = Operation.NOP;
				else
				{
					// Was not set before, thus means DELETE is actually a NOP. Write-through to propagate this
					operation = Operation.REMOVE_WRITE_THROUGH;
				}
			}
		}

		valueNew = null;
	}

	@Override
	public V getValue()
	{
		if (valueNew == null && loader != null)
		{
			valueNew = loader.load(key);
			operation = Operation.LOAD;
		}
		
		if (operation == Operation.NOP)
			operation = Operation.GET; // (Only) remember the GET if we do not have more relevant operations 
		return this.valueNew;
	}
	
	@Override
	public void setValue(V value)
	{
		if (value == null)
			throw new NullPointerException("value is null in setValue()");
		
		operation = Operation.SET;
		this.valueNew = value;
	}

	private V oldValue()
	{
		return value;
	}
	
	/**
	 * Returns the operation that should be performed on this MutableEntry after the EntryProcessor has returned.
	 * A entry that was not changed by an EntryProcessor returns {@link Operation#NOP}, which means no operation
	 * should take place
	 *  
	 * @return The desired Operation
	 */
	public Operation operation()
	{
		return operation;
	}
} public class ListenerCollection<K,V>
{
	//@GuardedBy("this")
	private final Set<ListenerEntry<K,V> > listeners = Collections.newSetFromMap(new ConcurrentHashMap<ListenerEntry<K,V>, Boolean>());
	private final Builder<K, V> builder;
	private final Cache<K, V> tcache;
	private final TCacheJSR107<K, V> jsr107cache;
	/// listenerPresentMask is a data structure to quickly lookup which kinds of listeners have been registered. Lookup time is O(1).
	private volatile short listenerPresentMask = 0;

	/**
	 * Creates a ListenerCollection that consists of all listeners from builder.getCacheEntryListenerConfigurations().
	 * 
	 * @param tcache The Cache
	 * @param builder The Builder of the given tcache
	 */
	public ListenerCollection(Cache<K,V> tcache, Builder<K,V> builder)
	{
		this.builder = builder;
		this.tcache = tcache;
		this.jsr107cache = tcache.jsr107cache();
		
	    for (Iterator<CacheEntryListenerConfiguration<K, V>> it = builder.getCacheEntryListenerConfigurations().iterator(); it.hasNext(); )
	    {
	    	enableCacheEntryListener(it.next());
	    }
	}


	/**
	 * Deregisters a cache listener.
	 * @param listenerConfiguration The Cache Listener
	 */
	public synchronized void deregisterCacheEntryListener(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		throwISEwhenClosed();

		Iterator<ListenerEntry<K, V>> it = listeners.iterator();
		while (it.hasNext())
		{
			ListenerEntry<K, V> listenerEntry = it.next();
			if (listenerConfiguration.equals(listenerEntry.getConfig()))
			{
				listenerEntry.shutdown();
				it.remove();
				// Reflect listener change in the configuration, as required by JSR107
				builder.removeCacheEntryListenerConfiguration(listenerConfiguration);
				break; // Can be only one, as it is in the Spec that Listeners must not added twice.
			}
		}

		// Removing a listener invalidates the lookup array (just like in a bloom filter), thus rebuild it
		rebuildListenerPresent();		
	}


	/**
	 * Rebuild the listenerPresent lookup array 
	 */
	private void rebuildListenerPresent()
	{
		short listenerPresentXnew = 0;
		for (ListenerEntry<K, V> listener : listeners)
		{
			for (EventType eventType : EventType.values())
			{
				if (listener.isListeningFor(eventType))
				{
					listenerPresentXnew |= (1 << eventType.ordinal());
				}
			}
		}

		listenerPresentMask = listenerPresentXnew;
	}


	/**
	 * Registers a cache listener.
	 * @param listenerConfiguration The Cache Listener
	 */
	public synchronized void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		throwISEwhenClosed();
		
		boolean added = enableCacheEntryListener(listenerConfiguration);
		if (!added)
		{
			throw new IllegalArgumentException("Cache entry listener may not be added twice to " + tcache.id() + ": "+ listenerConfiguration);
		}
		else
		{
			// Reflect listener change in the configuration, as required by JSR107
			builder.addCacheEntryListenerConfiguration(listenerConfiguration);
		}
	}
	
	/**
	 * Enables a listener, without adding it to the Configuration. An  enabled listener can send events after this method returns.  
	 * The caller must make sure that the
	 * corresponding Configuration object reflects the change.
	 * 
	 * @param listenerConfiguration
	 * @return
	 */
	private synchronized boolean enableCacheEntryListener(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		DispatchMode dispatchMode = listenerConfiguration.isSynchronous() ? DispatchMode.SYNC : DispatchMode.ASYNC_TIMED;
		ListenerEntry<K, V> newListener = new ListenerEntry<K, V>(listenerConfiguration, tcache, dispatchMode);
		boolean added = listeners.add(newListener);
		for (EventType eventType : EventType.values())
		{
			if (newListener.isListeningFor(eventType))
			{
				listenerPresentMask |= (1 << eventType.ordinal());
			}
		}

		return added;
	}
	
	public void dispatchEvent(EventType eventType, K key, V value)
	{
		// Only start dispatching if we have a listener for it. Try to avoid garbage (TCacheEntryEvent) if nobody is interested in it.
		if (hasListenerFor(eventType))
			dispatchEventToListeners(new TCacheEntryEvent<K, V>(jsr107cache, eventType, key, value));
	}


	public void dispatchEvent(EventType eventType, K key, V value, V oldValue)
	{
		// Only start dispatching if we have a listener for it. Try to avoid garbage (TCacheEntryEvent) if nobody is interested in it.
		if (hasListenerFor(eventType))
			dispatchEventToListeners(new TCacheEntryEvent<K, V>(jsr107cache, eventType, key, value, oldValue));
	}

	/**
	 * Notifies all listeners that a given EventType has happened for all the given entries.
	 * You can force all listeners to Async mode, but should only do this if it is compliant with JSR107.
	 * <p>
	 * <b>IMPORTANT PERFORMANCE NOTE</b>: To avoid unnecessary object creation (TCacheEntryEvent and the event
	 * Iterable), you SHOULD check with {@link #hasListenerFor(EventType)} whether there is any Listener
	 * interested in the given eventType. If not, do you can spare to create the TCacheEntryEvent events.
	 * 
	 * @param entries The key-value pairs for which to send events
	 * @param eventType The event Type
	 * @param forceAsync Force async mode
	 */
	public void dispatchEvents(Map<K, V> entries, EventType eventType, boolean forceAsync)
	{
		if (!hasListenerFor(eventType))
		{
			return;
		}
		
		List<TCacheEntryEvent<K,V>> events = new ArrayList<>(entries.size());
		for (Entry<K, V> entry : entries.entrySet())
		{
			K key = entry.getKey();
			V value = entry.getValue();
			TCacheEntryEvent<K,V> event = new TCacheEntryEvent<>(jsr107cache, eventType, key, value);
			events.add(event);
		}
		dispatchEventsToListeners(events, eventType, forceAsync);
	}
	
	
	private void dispatchEventToListeners(TCacheEntryEvent<K, V> event)
	{
		for (ListenerEntry<K, V> listener : listeners)
		{
			listener.dispatch(event);
		}
	}

	/**
	 * Dispatch the events to all Listeners. You can force all listeners to Async mode, but should only
	 * do this if it is compliant with JSR107
	 * <p>
	 * <b>IMPORTANT PERFORMANCE NOTE</b>: To avoid unnecessary object creation (TCacheEntryEvent and the event
	 * Iterable), you SHOULD check with {@link #hasListenerFor(EventType)} whether there is any Listener
	 * interested in the given eventType. If not, do you can spare to create the TCacheEntryEvent events.
	 * 
	 * @param events The events to send
	 * @param eventType The event Type
	 * @param forceAsync Force async mode
	 */
	private void dispatchEventsToListeners(Iterable<TCacheEntryEvent<K, V>> events, EventType eventType, boolean forceAsync)
	{
		// Only start dispatching if we have a listener for it.
		if (!hasListenerFor(eventType))
			return;

		for (ListenerEntry<K, V> listener : listeners)
		{
			listener.dispatch(events, eventType, forceAsync);
		}
	}
	

	/**
	 * Checks whether this ListenerCollection includes at least one Listener for the given EventType.
	 *  
	 * @param eventType The EventType to check
	 * @return true if at least one Listener is interested in the EventType
	 */
	public boolean hasListenerFor(EventType eventType)
	{
		int present = listenerPresentMask & (1 << eventType.ordinal());
		return present != 0;
	}
	
	/**
	 * Returns the number of listeners in this ListenerCollection.
	 * @return The number of listeners
	 */
	public int size()
	{
		return listeners.size();
	}
	
	/**
	 * Returns normally with no side effects if this cache is open. Throws IllegalStateException if it is closed.
	 */
	private void throwISEwhenClosed()
	{
		if (tcache.isClosed())
			throw new IllegalStateException("Cache already closed: " + tcache.id());
	}

	public void shutdown()
	{
		for (ListenerEntry<K, V> listener : listeners)
		{
			listener.shutdown();
		}
	}

} public class TCacheStatisticsBean implements TCacheStatisticsMXBean, Serializable
{
	private static final long serialVersionUID = -2459622086065310568L;
	
	final private StatisticsCalculator statistics;
	final transient private Cache<?,?> tcache;
	final private StatisticsAveragingMode averagingMode; 
	
	public enum StatisticsAveragingMode { JSR107, PER_MINUTE } 
	
	public TCacheStatisticsBean(Cache<?,?> tcache, StatisticsCalculator statisticsCalculator, StatisticsAveragingMode averagingMode)
	{
		this.tcache = tcache;
		this.statistics = statisticsCalculator;
		this.averagingMode = averagingMode;
	}

	@Override
	public void clear()
	{
		statistics.clear();
	}

	@Override
	public long getCacheHits()
	{
		return statistics.getHitCount();
	}

	@Override
	public float getCacheHitPercentage()
	{
		if (averagingMode == StatisticsAveragingMode.PER_MINUTE)
		{
			TCacheStatistics statistics2 = tcache.statistics();
			return statistics2.getHitRatio();
		}
		else
		{
			return safeRate(getCacheHits(), getCacheGets(), 0);
		}
	}

	/**
	 * Divide divident by divisor and return it. If divisor is 0, return the defaultValue instead.
	 */
	private float safeRate(long divident, long divisor, int defaultValue)
	{
		if (divisor == 0)
			return defaultValue;
		else
			return 100F * divident / divisor;
	}

	@Override
	public long getCacheMisses()
	{
		return statistics.getMissCount();
	}

	@Override
	public float getCacheMissPercentage()
	{
		if (averagingMode == StatisticsAveragingMode.PER_MINUTE)
		{
			if (getCacheGets() == 0)
				return 0; // special treatment, as the code below would calculate 100 (100F - hitRatio = 100) 
			
			TCacheStatistics statistics2 = tcache.statistics();
			return 100F - statistics2.getHitRatio();
		}
		else
		{
			return safeRate(getCacheMisses(), getCacheGets(), 0);
		}
	}

	@Override
	public long getCacheGets()
	{
		return statistics.getHitCount() + statistics.getMissCount();
	}

	@Override
	public long getCachePuts()
	{
		return statistics.getPutCount();
	}

	@Override
	public long getCacheRemovals()
	{
		return statistics.getRemoveCount();
	}

	/**
	 * Implementation note:
	 * Evictions in TCache are "evictions + drops".
	 * Drops occur, when the Cache is full and the Cache is configured with DROP as {@link com.trivago.triava.tcache.JamPolicy}.
	 * Drops can be considered as "eviction of the newest element".
	 */
	@Override
	public long getCacheEvictions()
	{
		TCacheStatistics statistics2 = tcache.statistics();
		return statistics2.getEvictionCount() + statistics2.getDropCount();
	}

	@Override
	public float getAverageGetTime()
	{
		// not measured, as this can limit throughput, as calls like System.nanoTime() can be very expensive
		return 0;
	}

	@Override
	public float getAveragePutTime()
	{
		// not measured, as this can limit throughput, as calls like System.nanoTime() can be very expensive
		return 0;
	}

	@Override
	public float getAverageRemoveTime()
	{
		// not measured, as this can limit throughput, as calls like System.nanoTime() can be very expensive
		return 0;
	}

	@Override
	public int getSize()
	{
		return tcache.size();
	}

} public enum StatisticsAveragingMode { JSR107, PER_MINUTE }  public enum ChangeStatus
{
	CREATED, CHANGED, UNCHANGED, CAS_FAILED_EQUALS
} public class KeyValueUtil<K, V>
{
	String id;
	
	/**
	 * Creates a typed KeyValueUtil for the given Cache.
	 * 
	 * @param id The id (or cacheName) of the Cache
	 */
	public KeyValueUtil(String id)
	{
		this.id = id;
	}
	
	/**
	 * Checks whether key or value are null. If at least one parameter is null, NullPointerException is thrown. Otherwise
	 * this method returns without any side-effects.
	 * 
	 * <p>
	 * JSR107 mandates to throw NullPointerException. This is not in the Javadoc of JSR107, but in the
	 * general part of the specification document:
	 * "Any attempt to use null for keys or values will result in a NullPointerException being thrown, regardless of the use." 
	 * 
	 * @param key The key
	 * @param value The value
	 * @throws  NullPointerException if at least one parameter is null 
	 */
	public void verifyKeyAndValueNotNull(K key, V value)
	{
		verifyKeyNotNull(key);
		verifyValueNotNull(value);
	}
	
	/**
	 * Checks whether key is null. If it is null, NullPointerException is thrown. Otherwise
	 * this method returns without any side-effects.
	 * 
	 * <p>
	 * JSR107 mandates to throw NullPointerException. This is not in the Javadoc of JSR107, but in the
	 * general part of the specification document:
	 * "Any attempt to use null for keys or values will result in a NullPointerException being thrown, regardless of the use." 
	 * 
	 * @param key The key
	 * @throws  NullPointerException if key is null 
	 */
	public void verifyKeyNotNull(K key)
	{
		if(key == null)
		{
			throw new NullPointerException("null key is not allowed. cache=" + id);
		}
	}

	/**
	 * Checks whether key is null. If it is null, NullPointerException is thrown. Otherwise
	 * this method returns without any side-effects.
	 * 
	 * <p>
	 * JSR107 mandates to throw NullPointerException. This is not in the Javadoc of JSR107, but in the
	 * general part of the specification document:
	 * "Any attempt to use null for keys or values will result in a NullPointerException being thrown, regardless of the use." 
	 * 
	 * @param value The value
	 * @throws  NullPointerException if value is null 
	 */
	public void verifyValueNotNull(V value)
	{
		if(value == null)
		{
			throw new NullPointerException("null value is not allowed. cache=" + id);
		}
	}

	public void verifyKeysNotNull(Set<? extends K> keys)
	{
		if(keys == null)
		{
			throw new NullPointerException("null key set is not allowed. cache=" + id);
		}
	}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.CompleteConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.event.EventType;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CacheWriterException;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;
import com.trivago.triava.tcache.action.Action;
import com.trivago.triava.tcache.action.ActionRunner;
import com.trivago.triava.tcache.action.DeleteAction;
import com.trivago.triava.tcache.action.DeleteOnValueAction;
import com.trivago.triava.tcache.action.GetAndPutAction;
import com.trivago.triava.tcache.action.GetAndRemoveAction;
import com.trivago.triava.tcache.action.PutAction;
import com.trivago.triava.tcache.action.ReplaceAction;
import com.trivago.triava.tcache.action.WriteBehindActionRunner;
import com.trivago.triava.tcache.action.WriteThroughActionRunner;
import com.trivago.triava.tcache.core.Builder;
import com.trivago.triava.tcache.core.Holders;
import com.trivago.triava.tcache.core.NopCacheWriter;
import com.trivago.triava.tcache.core.TCacheConfigurationBean;
import com.trivago.triava.tcache.core.TCacheEntryIterator;
import com.trivago.triava.tcache.core.TCacheJSR107Entry;
import com.trivago.triava.tcache.core.TCacheJSR107MutableEntry;
import com.trivago.triava.tcache.event.ListenerCollection;
import com.trivago.triava.tcache.statistics.TCacheStatisticsBean;
import com.trivago.triava.tcache.statistics.TCacheStatisticsBean.StatisticsAveragingMode;
import com.trivago.triava.tcache.util.ChangeStatus;
import com.trivago.triava.tcache.util.KeyValueUtil;
					Map<K, V> mapToAdd = (holders.oldHolder == null) ? createdEntries : updatedEntries;
					if (mapToAdd != null) // mapToAdd could be null here, if anyListenerInterested == true, but
											// only one (iow: CREATED xor UPDATED)
						mapToAdd.put(key, value);
				}
				// else: immediately expired
			}
		}

		// -2- Notify Listeners -----------------------------------

		if (createdEntries != null)
			tcache.listeners.dispatchEvents(createdEntries, EventType.CREATED, false);
		if (updatedEntries != null)
			tcache.listeners.dispatchEvents(updatedEntries, EventType.UPDATED, false);

		if (cacheWriterException != null)
		{
			throw cacheWriterException;
		}
	}

	private void verifyEntriesNotNull(Map<? extends K, ? extends V> entries)
	{
		// JSR107 spec clarification needed. The TCK check PutTest.putAll_NullKey() requires a total failure, and disallows partial success.
		// This is not explicitly in the Specs. Until this clarification is done, we do an explicit key and value null check before starting to put values in the cache.
		for (java.util.Map.Entry<? extends K, ? extends V> entry : entries.entrySet())
		{
			K key = entry.getKey();
			V value = entry.getValue();
			kvUtil.verifyKeyAndValueNotNull(key, value);
		}
	}

	private void verifyKeysNotNull(Set<? extends K> keys)
	{
		// JSR107 spec clarification needed. The TCK check removeAll_1arg_NullKey(org.jsr107.tck.RemoveTest)
		// requires a total failure, and disallows partial success.
		// This is not explicitly in the Specs. Until this clarification is done, we do an explicit key and
		// value null check before starting to put values in the cache.
		for (K key : keys)
		{
			kvUtil.verifyKeyNotNull(key);
		}
	}

	@Override
	public boolean putIfAbsent(K key, V value)
	{
		throwISEwhenClosed();

		PutAction<K,V,Object> action = new PutAction<>(key, value, EventType.CREATED, false);
		boolean added = false;
		if (actionRunnerWriteBehind.preMutate(action))
		{
			Holders<V> holders = tcache.putIfAbsentH(key, value);
			// For JSR107 putIfAbsent() should return whether a value was set.
			// It is set when the old holder was null AND the new holder is not null.
			// The latter case (new holder == null) can happen if it immediately expires.
			added = holders.oldHolder == null && holders.newHolder != null;
			if (added)
				actionRunnerWriteBehind.postMutate(action);
		}

		return added;
	}

	@Override
	public void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> listenerConfiguration)
	{
		throwISEwhenClosed();
		
		tcache.listeners.registerCacheEntryListener(listenerConfiguration);
		refreshActionRunners();
	}


	/**
	 * Returns normally with no side effects if this cache is open. Throws IllegalStateException if it is
	 * closed.
	 */
	private void throwISEwhenClosed()
	{
		if (isClosed())
			throw new IllegalStateException("Cache already closed: " + tcache.id());
	}

	@Override
	public boolean remove(K key)
	{
		return remove(key, true);
	}

	boolean remove(K key, boolean mutateLocal)
	{
		throwISEwhenClosed();
		kvUtil.verifyKeyNotNull(key);
		
		return removeInternal(key, null, mutateLocal);
	}

	
	@Override
	public boolean remove(K key, V value)
	{
		throwISEwhenClosed();
		kvUtil.verifyKeyAndValueNotNull(key, value);

		return removeInternal(key, value, true);
	}

	/**
	 * Internal remove code. Handles both 1-arg and 2-arg remove() methods.
	 * @param key
	 * @param value
	 * @param mutateLocal
	 * @return
	 */
	boolean removeInternal(K key, V value, boolean mutateLocal)
	{
		final DeleteAction<K,V,Object> action;