class CacheDataContainer<K,V> {

        private final String name;
        private final boolean sizeLimit;
        private final int maxSize;
        private final boolean traceExecution;
        private final Logger logger;

        private final ConcurrentHashMap<K,CacheEntry<V>> container;
        private final Object[] fifo;
        private int fifoPointer;


        CacheDataContainer(final String name, final int initialCapacity,
                final int maxSize, final boolean traceExecution, final Logger logger) {

            super();

            this.name = name;
            this.container = new ConcurrentHashMap<K,CacheEntry<V>>(initialCapacity, 0.9f, 2);
            this.maxSize = maxSize;
            this.sizeLimit = (maxSize >= 0);
            if (this.sizeLimit) {
                this.fifo = new Object[this.maxSize];
                Arrays.fill(this.fifo, null);
            } else {
                this.fifo = null;
            }
            this.fifoPointer = 0;
            this.traceExecution = traceExecution;
            this.logger = logger;

        }


        public CacheEntry<V> get(final Object key) {
            // FIFO is not used for this --> better performance, but no LRU (only insertion order will apply)
            return this.container.get(key);
        }


        public Set<K> keySet() {
            // This 'strange' cast is needed in order to keep compatibility with Java 6 and 7, when compiling with
            // Java 8. The reason is, the return type of Java 8's ConcurrentHashMap#keySet() changed to a class
            // called KeySetView, implementing java.util.Set but new in Java 8. This made this code throw a
            // java.lang.NoSuchMethodError when executed in Java 6 or 7.
            // By adding the cast, we are binding bytecode not to the specific keySet() method of ConcurrentHashMap,
            // but to the one defined at the java.util.Map interface, which simply returns java.util.Set.
            return ((Map<K,CacheEntry<V>>)this.container).keySet();
        }


        public int put(final K key, final CacheEntry<V> value) {
            if (this.traceExecution) {
                return putWithTracing(key, value);
            }
            return putWithoutTracing(key, value);
        }


        private int putWithoutTracing(final K key, final CacheEntry<V> value) {<FILL_FUNCTION_BODY>}

        private synchronized int putWithTracing(final K key, final CacheEntry<V> value) {

            final CacheEntry<V> existing = this.container.putIfAbsent(key, value);
            if (existing == null) {
                if (this.sizeLimit) {
                    final Object removedKey = this.fifo[this.fifoPointer];
                    if (removedKey != null) {
                        final CacheEntry<V> removed = this.container.remove(removedKey);
                        if (removed != null) {
                            final Integer newSize = Integer.valueOf(this.container.size());
                            this.logger.trace(
                                    "[THYMELEAF][{}][{}][CACHE_REMOVE][{}] Max size exceeded for cache \"{}\". Removing entry for key \"{}\". New size is {}.",
                                    new Object[] {TemplateEngine.threadIndex(), this.name, newSize, this.name, removedKey, newSize});
                        }
                    }
                    this.fifo[this.fifoPointer] = key;
                    this.fifoPointer = (this.fifoPointer + 1) % this.maxSize;
                }
            }
            return this.container.size();

        }


        public int remove(final K key) {
            if (this.traceExecution) {
                return removeWithTracing(key);
            }
            return removeWithoutTracing(key);
        }


        private int removeWithoutTracing(final K key) {
            // FIFO is also updated to avoid 'removed' keys remaining at FIFO (which could end up reducing cache size to 1)
            final CacheEntry<V> removed = this.container.remove(key);
            if (removed != null) {
                if (this.sizeLimit && key != null) {
                    for (int i = 0; i < this.maxSize; i++) {
                        if (key.equals(this.fifo[i])) {
                            this.fifo[i] = null;
                            break;
                        }
                    }
                }
            }
            return -1;
        }


        private synchronized int removeWithTracing(final K key) {
            // FIFO is also updated to avoid 'removed' keys remaining at FIFO (which could end up reducing cache size to 1)
            final CacheEntry<V> removed = this.container.remove(key);
            if (removed == null) {
                // When tracing is active, this means nothing was removed
                return -1;
            }
            if (this.sizeLimit && key != null) {
                for (int i = 0; i < this.maxSize; i++) {
                    if (key.equals(this.fifo[i])) {
                        this.fifo[i] = null;
                        break;
                    }
                }
            }
            return this.container.size();
        }


        public void clear() {
            this.container.clear();
        }


        public int size() {
            return this.container.size();
        }

    }

            // If we are not tracing, it's better to avoid the size() operation which has
            // some performance implications in ConcurrentHashMap (iteration and counting these maps
            // is slow if they are big)

            final CacheEntry<V> existing = this.container.putIfAbsent(key, value);
            if (existing != null) {
                // When not in 'trace' mode, will always return -1
                return -1;
            }

            if (this.sizeLimit) {
                synchronized (this.fifo) {
                    final Object removedKey = this.fifo[this.fifoPointer];
                    if (removedKey != null) {
                        this.container.remove(removedKey);
                    }
                    this.fifo[this.fifoPointer] = key;
                    this.fifoPointer = (this.fifoPointer + 1) % this.maxSize;
                }
            }

            return -1;

        
<no_super_class>