/*
 * Copyright 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.internal.reflect;

import org.jspecify.annotations.Nullable;

import java.util.WeakHashMap;

/**
 * A generic purpose, thread-safe cache which is aimed at storing information
 * about a class. The cache is a hierarchical cache, which key is a composite
 * of a receiver, and argument types. All those, key or arguments, are kept
 * in a weak reference, allowing the GC to recover memory if required.
 *
 * @param <T> the type of the element stored in the cache.
 */
public abstract class ReflectionCache<T extends CachedInvokable<?>> {
    private final Object lock = new Object();

    private final WeaklyClassReferencingCache cache = new WeaklyClassReferencingCache();

    public T get(final Class<?> receiver, final Class<?>[] key) {
        synchronized (lock) {
            return cache.get(receiver, key);
        }
    }

    protected abstract T create(Class<?> receiver, Class<?>[] key);

    public int size() {
        return cache.size();
    }

    private class WeaklyClassReferencingCache extends WeakHashMap<Class<?>, CacheEntry> {

        public T get(Class<?> receiver, Class<?>[] classes) {
            WeaklyClassReferencingCache cur = this;
            CacheEntry last = fetchNext(cur, receiver);
            for (Class<?> aClass : classes) {
                cur = last.table;
                last = fetchNext(cur, aClass);
            }
            if (last.value == null || last.value.getMethod() == null) {
                last.value = create(receiver, classes);
            }
            return last.value;
        }

        private CacheEntry fetchNext(WeaklyClassReferencingCache cur, Class<?> aClass) {
            CacheEntry last;
            last = cur.get(aClass);
            if (last == null) {
                last = new CacheEntry();
                cur.put(aClass, last);
            }
            return last;
        }
    }

    private class CacheEntry {
        private final WeaklyClassReferencingCache table = new WeaklyClassReferencingCache();
        private @Nullable T value;
    }

}
