package org.skywalking.apm.agent.core.plugin.interceptor.assist;

import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodInvokeContext;
import org.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext;
import org.skywalking.apm.agent.core.plugin.interceptor.InterceptorException;

/**
 * {@link NoConcurrencyAccessObject} is method invocation counter,
 * when {@link #whenEnter(EnhancedClassInstanceContext, InstanceMethodInvokeContext)} , counter + 1;
 * and when {@link #whenExist(EnhancedClassInstanceContext)}  , counter -1;
 * <p>
 * When, and only when, the first enter and last exist, also meaning first access,
 * the {@link #enter(EnhancedClassInstanceContext, InstanceMethodInvokeContext)}
 * and {@link #exit()} are called.
 *
 * @author wusheng
 */
public abstract class NoConcurrencyAccessObject {
    private static final String INVOKE_COUNTER_KEY = "__$invokeCounterKey";

    public void whenEnter(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext) {
        if (!context.isContain(INVOKE_COUNTER_KEY)) {
            context.set(INVOKE_COUNTER_KEY, 0);
        }
        int counter = context.get(INVOKE_COUNTER_KEY,
            Integer.class);
        if (++counter == 1) {
            enter(context, interceptorContext);
        }
        context.set(INVOKE_COUNTER_KEY, counter);
    }

    public void whenExist(EnhancedClassInstanceContext context) {
        if (!context.isContain(INVOKE_COUNTER_KEY)) {
            throw new InterceptorException(
                "key=INVOKE_COUNTER_KEY not found is context. unexpected situation.");
        }
        int counter = context.get(INVOKE_COUNTER_KEY,
            Integer.class);
        if (--counter == 0) {
            exit();
        }
        context.set(INVOKE_COUNTER_KEY, counter);
    }

    protected abstract void enter(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext);

    protected abstract void exit();
}
