ObjectFactory.java

/*
 *    Copyright 2006-2026 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
 *
 *       https://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.mybatis.generator.internal;

import static org.mybatis.generator.internal.util.StringUtility.stringValueOrElse;
import static org.mybatis.generator.internal.util.messages.Messages.getString;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.ConnectionFactory;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.JavaFormatter;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.KnownRuntime;
import org.mybatis.generator.api.KotlinFormatter;
import org.mybatis.generator.api.Plugin;
import org.mybatis.generator.api.XmlFormatter;
import org.mybatis.generator.config.CommentGeneratorConfiguration;
import org.mybatis.generator.config.ConnectionFactoryConfiguration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.Defaults;
import org.mybatis.generator.config.JavaTypeResolverConfiguration;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.exception.InternalException;

/**
 * This class creates the different objects needed by the generator.
 *
 * @author Jeff Butler
 */
public class ObjectFactory {

    private static final List<ClassLoader> externalClassLoaders;

    static {
        externalClassLoaders = new ArrayList<>();
    }

    /**
     * Utility class. No instances allowed.
     */
    private ObjectFactory() {
        super();
    }

    /**
     * Clears the class loaders.  This method should be called at the beginning of
     * a generation run so that and change to the classloading configuration
     * will be reflected.  For example, if the eclipse launcher changes configuration
     * it might not be updated if eclipse hasn't been restarted.
     *
     */
    public static void reset() {
        externalClassLoaders.clear();
    }

    /**
     * Adds a custom classloader to the collection of classloaders searched for "external" classes. These are classes
     * that do not depend on any of the generator's classes or interfaces. Examples are JDBC drivers, root classes, root
     * interfaces, etc.
     *
     * @param classLoader
     *            the class loader
     */
    public static synchronized void addExternalClassLoader(ClassLoader classLoader) {
        externalClassLoaders.add(classLoader);
    }

    /**
     * Returns a class loaded from the context classloader, or the classloader supplied by a client. This is
     * appropriate for JDBC drivers, model root classes, etc. It is not appropriate for any class that extends one of
     * the supplied classes or interfaces.
     *
     * @param type
     *            the type
     * @return the Class loaded from the external classloader
     * @throws ClassNotFoundException
     *             the class not found exception
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> externalClassForName(String type) throws ClassNotFoundException {
        Class<T> clazz;

        for (ClassLoader classLoader : externalClassLoaders) {
            try {
                clazz = (Class<T>) Class.forName(type, true, classLoader);
                return clazz;
            } catch (Exception e) {
                // ignore - fail safe below
            }
        }

        return internalClassForName(type);
    }

    public static <T> T createExternalObject(String type) {
        T answer;

        try {
            Class<T> clazz = externalClassForName(type);
            answer = clazz.getConstructor().newInstance();
        } catch (Exception e) {
            throw new InternalException(getString("RuntimeError.6", type), e); //$NON-NLS-1$
        }

        return answer;
    }

    @SuppressWarnings("unchecked")
    public static <T> Class<T> internalClassForName(String type) throws ClassNotFoundException {
        Class<?> clazz;
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            clazz = Class.forName(type, true, cl);
        } catch (Exception e) {
            // ignore - failsafe below
            clazz = null;
        }

        if (clazz == null) {
            clazz = Class.forName(type, true, ObjectFactory.class.getClassLoader());
        }

        return (Class<T>) clazz;
    }

    public static Optional<URL> getResource(String resource) {
        URL url;

        for (ClassLoader classLoader : externalClassLoaders) {
            url = classLoader.getResource(resource);
            if (url != null) {
                return Optional.of(url);
            }
        }

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        url = cl.getResource(resource);

        if (url == null) {
            url = ObjectFactory.class.getClassLoader().getResource(resource);
        }

        return Optional.ofNullable(url);
    }

    public static <T> T createInternalObject(String type) {
        T answer;

        try {
            Class<T> clazz = internalClassForName(type);
            answer = clazz.getConstructor().newInstance();
        } catch (Exception e) {
            throw new InternalException(getString("RuntimeError.6", type), e); //$NON-NLS-1$
        }

        return answer;
    }

    public static JavaTypeResolver createJavaTypeResolver(Context context, List<String> warnings) {
        String type = context.getJavaTypeResolverConfiguration()
                .map(JavaTypeResolverConfiguration::getImplementationType)
                .orElse(Defaults.DEFAULT_JAVA_TYPE_RESOLVER);

        JavaTypeResolver answer = createInternalObject(type);
        answer.setWarnings(warnings);

        context.getJavaTypeResolverConfiguration()
                .ifPresent(c -> answer.addConfigurationProperties(c.getProperties()));

        answer.setContext(context);

        return answer;
    }

    public static Plugin createPlugin(Context context, PluginConfiguration pluginConfiguration,
                                      CommentGenerator commentGenerator, KnownRuntime knownRuntime) {
        Plugin plugin = createInternalObject(pluginConfiguration.getConfigurationType().orElseThrow());
        plugin.setContext(context);
        plugin.setProperties(pluginConfiguration.getProperties());
        plugin.setCommentGenerator(commentGenerator);
        plugin.setKnownRuntime(knownRuntime);
        return plugin;
    }

    public static CommentGenerator createCommentGenerator(Context context) {
        CommentGenerator answer;

        String type = context.getCommentGeneratorConfiguration()
                .map(CommentGeneratorConfiguration::getImplementationType)
                .orElse(Defaults.DEFAULT_COMMENT_GENERATOR);

        answer = createInternalObject(type);

        context.getCommentGeneratorConfiguration()
                .ifPresent(c -> answer.addConfigurationProperties(c.getProperties()));

        return answer;
    }

    public static ConnectionFactory createConnectionFactory(ConnectionFactoryConfiguration config) {
        ConnectionFactory answer;

        String type = config.getImplementationType();

        answer = createInternalObject(type);
        answer.addConfigurationProperties(config.getProperties());

        return answer;
    }

    public static JavaFormatter createJavaFormatter(Context context) {
        String type = stringValueOrElse(context.getProperty(PropertyRegistry.CONTEXT_JAVA_FORMATTER),
                Defaults.DEFAULT_JAVA_FORMATTER);
        JavaFormatter answer = createInternalObject(type);

        answer.setContext(context);

        return answer;
    }

    public static KotlinFormatter createKotlinFormatter(Context context) {
        String type = stringValueOrElse(context.getProperty(PropertyRegistry.CONTEXT_KOTLIN_FORMATTER),
                Defaults.DEFAULT_KOTLIN_FORMATTER);
        KotlinFormatter answer = createInternalObject(type);

        answer.setContext(context);

        return answer;
    }

    public static XmlFormatter createXmlFormatter(Context context) {
        String type = stringValueOrElse(context.getProperty(PropertyRegistry.CONTEXT_XML_FORMATTER),
                Defaults.DEFAULT_XML_FORMATTER);
        XmlFormatter answer = createInternalObject(type);

        answer.setContext(context);

        return answer;
    }

    public static IntrospectedColumn createIntrospectedColumn(Context context) {
        String type = context.getIntrospectedColumnImpl().orElse(IntrospectedColumn.class.getName());
        IntrospectedColumn answer = createInternalObject(type);
        answer.setContext(context);

        return answer;
    }
}