package quant.rich.emoney.config;

import java.util.HashSet;
import java.util.Set;

import org.springframework.beans.factory.BeanCreationException;

import lombok.extern.slf4j.Slf4j;

/**
 * To prevent bean cyclic instantiation through BeanFactory:<p>
 * The reason is that some configuration classes are deserialized via json. 
 * During this process, the default behavior is to call their no-argument 
 * constructors. Inside these constructors, it’s likely that static methods 
 * from SpringContextHolder are invoked to obtain other configuration classes. 
 * However, those other configuration classes may in turn call the same static
 * methods of SpringContextHolder to obtain yet other configuration classes.<p>
 * At this point, since the configuration class is still {@code null}, Spring attempts 
 * once again to produce the instance of this class via BeanFactory, leading to 
 * a cyclic instantiation process and eventually causing a stack overflow.<p>
 * Since SpringContextHolder actually operates outside of Spring’s management lifecycle,
 * it is difficult to detect this issue at runtime. Therefore, this class should 
 * be used within the Factory’s getObject method, where an exception is thrown 
 * if cyclic instantiation occurs. The implementation of this class is based on ThreadLocal.
 * 
 * @author Doghole
 * @see ConfigServiceFactoryBean
 */
@Slf4j
public class ConstructionGuard {
    private static final ThreadLocal<Set<Class<?>>> constructing = ThreadLocal.withInitial(HashSet::new);

    public static boolean isConstructing(Class<?> clazz) {
        return constructing.get().contains(clazz);
    }

    public static void enter(Class<?> clazz) {
        if (isConstructing(clazz)) {
            StringBuilder sb = new StringBuilder();
            sb.append("Class ")
              .append(clazz.getName())
              .append(" is being constructed but is seems like circular involving.");
            String msg = sb.toString();
            log.error(msg);
            throw new BeanCreationException(msg);
        }
        constructing.get().add(clazz);
    }

    public static void exit(Class<?> clazz) {
        constructing.get().remove(clazz);
    }
}