/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.struts2.spring;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ClassUtils;
import org.springframework.core.DecoratingClassLoader;

import java.lang.reflect.Constructor;

/**
 *  Same as DefaultListableBeanFactory, but it doesn't use the constructor and class cached in RootBeanDefinition
 */
public class ClassReloadingBeanFactory extends DefaultListableBeanFactory {
    @Override
    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
        Class beanClass = resolveBeanClass(mbd, beanName, null);

        if (mbd.getFactoryMethodName() != null) {
            return instantiateUsingFactoryMethod(beanName, mbd, args);
        }

        // Need to determine the constructor...
        Constructor[] constructors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (constructors != null ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
            return autowireConstructor(beanName, mbd, constructors, args);
        }

        // No special handling: simply use no-arg constructor.
        return instantiateBean(beanName, mbd);
    }

    protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName, Class[] typesToMatch) {
        try {
            if (typesToMatch != null) {
                ClassLoader tempClassLoader = getTempClassLoader();
                if (tempClassLoader != null) {
                    if (tempClassLoader instanceof DecoratingClassLoader) {
                        DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader;
                        for (int i = 0; i < typesToMatch.length; i++) {
                            dcl.excludeClass(typesToMatch[i].getName());
                        }
                    }
                    String className = mbd.getBeanClassName();
                    return (className != null ? ClassUtils.forName(className, tempClassLoader) : null);
                }
            }
            return mbd.resolveBeanClass(getBeanClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
        }
        catch (LinkageError err) {
            throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
        }
    }
}
