/*
 * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.server;

import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Configurable;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Feature;

import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.spi.AutoDiscoverable;
import org.glassfish.jersey.internal.util.Producer;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.Tokenizer;
import org.glassfish.jersey.model.ContractProvider;
import org.glassfish.jersey.model.internal.CommonConfig;
import org.glassfish.jersey.model.internal.ComponentBag;
import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener;
import org.glassfish.jersey.server.internal.scanning.FilesScanner;
import org.glassfish.jersey.server.internal.scanning.PackageNamesScanner;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.uri.UriComponent;


/**
 * The resource configuration for configuring a web application.
 *
 * @author Paul Sandoz
 * @author Martin Matula
 * @author Michal Gajdos
 * @author Marek Potociar
 */
public class ResourceConfig extends Application implements Configurable<ResourceConfig>, ServerConfig {

    private static final Logger LOGGER = Logger.getLogger(ResourceConfig.class.getName());

    private transient Set<Class<?>> cachedClasses = null;
    private transient Set<Class<?>> cachedClassesView = null;
    private transient Set<Object> cachedSingletons = null;
    private transient Set<Object> cachedSingletonsView = null;

    private transient boolean resetFinders = false;

    private volatile State state;

    private static class State extends CommonConfig implements ServerConfig {

        private final Set<ResourceFinder> resourceFinders;

        private final Set<Resource> resources;
        private final Set<Resource> resourcesView;
        private volatile String applicationName;

        private volatile ClassLoader classLoader = null;

        public State() {
            super(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
            this.classLoader = AccessController.doPrivileged(ReflectionHelper.getContextClassLoaderPA());

            this.resourceFinders = new HashSet<>();

            this.resources = new HashSet<>();
            this.resourcesView = Collections.unmodifiableSet(this.resources);
        }

        public State(final State original) {
            super(original);
            this.classLoader = original.classLoader;
            this.applicationName = original.applicationName;

            this.resources = new HashSet<>(original.resources);
            this.resourcesView = Collections.unmodifiableSet(this.resources);

            this.resourceFinders = new HashSet<>(original.resourceFinders);
        }

        public void setClassLoader(final ClassLoader classLoader) {
            this.classLoader = classLoader;
        }

        public void setApplicationName(final String applicationName) {
            this.applicationName = applicationName;
        }

        public void registerResources(final Set<Resource> resources) {
            this.resources.addAll(resources);
        }

        public void registerFinder(final ResourceFinder resourceFinder) {
            this.resourceFinders.add(resourceFinder);
        }

        @Override
        protected Inflector<ContractProvider.Builder, ContractProvider> getModelEnhancer(final Class<?> componentClass) {
            return builder -> {
                if (builder.getScope() == null && builder.getContracts().isEmpty() && Resource.getPath(componentClass) != null) {
                    builder.scope(RequestScoped.class);
                }

                return builder.build();
            };
        }

        @Override
        public State loadFrom(final Configuration config) {
            super.loadFrom(config);
            this.resourceFinders.clear();
            this.resources.clear();

            State other = null;
            if (config instanceof ResourceConfig) {
                other = ((ResourceConfig) config).state;
            }
            if (config instanceof State) {
                other = (State) config;
            }

            if (other != null) {
                this.resourceFinders.addAll(other.resourceFinders);
                this.resources.addAll(other.resources);
            }

            return this;
        }

        @Override
        public final Set<Resource> getResources() {
            return resourcesView;
        }

        @Override
        public ServerConfig getConfiguration() {
            return this;
        }

        /**
         * Get the registered resource finders.
         *
         * @return registered resource finders.
         */
        public Set<ResourceFinder> getResourceFinders() {
            return resourceFinders;
        }

        /**
         * Get resource and provider class loader.
         *
         * @return class loader to be used when looking up the resource classes and providers.
         */
        public ClassLoader getClassLoader() {
            return classLoader;
        }

        private String getApplicationName() {
            return applicationName;
        }
    }

    private static final class ImmutableState extends State {

        private ImmutableState(final State original) {
            super(original);
        }

        @Override
        public void setClassLoader(final ClassLoader classLoader) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void registerResources(final Set<Resource> resources) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void registerFinder(final ResourceFinder resourceFinder) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State addProperties(final Map<String, ?> properties) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State property(final String name, final Object value) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class<?> componentClass) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class<?> componentClass, final int bindingPriority) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class<?> componentClass, final Class<?>... contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class<?> componentClass, final Map<Class<?>, Integer> contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final int bindingPriority) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final Class<?>... contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final Map<Class<?>, Integer> contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State setProperties(final Map<String, ?> properties) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void configureAutoDiscoverableProviders(final InjectionManager injectionManager,
                final Collection<AutoDiscoverable> autoDiscoverables, final boolean forcedOnly) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void configureMetaProviders(final InjectionManager injectionManager, final ManagedObjectsFinalizer finalizer) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }
    }

    /**
     * Returns a {@code ResourceConfig} instance for the supplied application.
     * <p/>
     * If the application is an instance of {@code ResourceConfig} the method returns defensive copy of the resource config.
     * Otherwise it creates a new {@code ResourceConfig} from the application.
     *
     * @param application Application to provide the {@code ResourceConfig} instance for.
     * @return ResourceConfig instance for the supplied application.
     */
    public static ResourceConfig forApplication(final Application application) {
        return application instanceof ResourceConfig
                ? ((ResourceConfig) application)
                : new WrappingResourceConfig(application, null, null);
    }

    /**
     * Returns a {@code ResourceConfig} instance wrapping the application of the supplied class.
     *
     * @param applicationClass Class representing a JAX-RS application.
     * @return ResourceConfig wrapping the JAX-RS application defined by the supplied class.
     */
    public static ResourceConfig forApplicationClass(final Class<? extends Application> applicationClass) {
        return new WrappingResourceConfig(null, applicationClass, null);
    }

    /**
     * Returns a {@code ResourceConfig} instance wrapping the application of the supplied class.
     * <p/>
     * This method provides an option of supplying the set of classes that should be returned from {@link #getClasses()}
     * method if the application defined by the supplied application class returns empty sets from
     * {@link jakarta.ws.rs.core.Application#getClasses()}
     * and {@link jakarta.ws.rs.core.Application#getSingletons()} methods.
     *
     * @param applicationClass Class representing a JAX-RS application.
     * @param defaultClasses   Default set of classes that should be returned from {@link #getClasses()} if the underlying
     *                         application does not provide any classes and singletons.
     * @return ResourceConfig wrapping the JAX-RS application defined by the supplied class.
     */
    public static ResourceConfig forApplicationClass(final Class<? extends Application> applicationClass,
                                                     final Set<Class<?>> defaultClasses) {
        return new WrappingResourceConfig(null, applicationClass, defaultClasses);
    }

    /**
     * Create a new resource configuration without any custom properties or
     * resource and provider classes.
     */
    public ResourceConfig() {
        this.state = new State();
    }

    /**
     * Create a new resource configuration initialized with a given set of
     * resource/provider classes.
     *
     * @param classes application-specific resource and/or provider classes.
     */
    public ResourceConfig(final Set<Class<?>> classes) {
        this();
        this.registerClasses(classes);
    }

    /**
     * Create a new resource configuration initialized with a given set of
     * resource/provider classes.
     *
     * @param classes application-specific resource and/or provider classes.
     */
    public ResourceConfig(final Class<?>... classes) {
        this(Arrays.stream(classes).collect(Collectors.toSet()));
    }

    /**
     * Create a defensive resource configuration copy initialized with a given {@code ResourceConfig}.
     *
     * @param original resource configuration to createAndInitialize a defensive copy from.
     */
    public ResourceConfig(final ResourceConfig original) {
        this.state = new State(original.state);
    }

    /**
     * Add properties to {@code ResourceConfig}.
     * <p/>
     * If any of the added properties exists already, old values of existing
     * properties will be replaced by new values.
     *
     * @param properties properties to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addProperties(final Map<String, Object> properties) {
        state.addProperties(properties);
        return this;
    }

    /**
     * Set new configuration properties replacing all previously set properties.
     *
     * @param properties new set of configuration properties. The content of
     *                   the map will replace any existing properties set on the configuration
     *                   instance.
     * @return the updated configuration instance.
     */
    public ResourceConfig setProperties(final Map<String, ?> properties) {
        state.setProperties(properties);
        return this;
    }

    @Override
    public ResourceConfig property(final String name, final Object value) {
        state.property(name, value);
        return this;
    }

    @Override
    public ResourceConfig register(final Class<?> componentClass) {
        invalidateCache();
        state.register(componentClass);
        return this;
    }

    @Override
    public ResourceConfig register(final Class<?> componentClass, final int bindingPriority) {
        invalidateCache();
        state.register(componentClass, bindingPriority);
        return this;
    }

    @Override
    public ResourceConfig register(final Class<?> componentClass, final Class<?>... contracts) {
        invalidateCache();
        state.register(componentClass, contracts);
        return this;
    }

    @Override
    public ResourceConfig register(final Class<?> componentClass, final Map<Class<?>, Integer> contracts) {
        invalidateCache();
        state.register(componentClass, contracts);
        return this;
    }

    @Override
    public ResourceConfig register(final Object component) {
        invalidateCache();
        state.register(component);
        return this;
    }

    @Override
    public ResourceConfig register(final Object component, final int bindingPriority) {
        invalidateCache();
        state.register(component, bindingPriority);
        return this;
    }

    @Override
    public ResourceConfig register(final Object component, final Class<?>... contracts) {
        invalidateCache();
        state.register(component, contracts);
        return this;
    }

    @Override
    public ResourceConfig register(final Object component, final Map<Class<?>, Integer> contracts) {
        invalidateCache();
        state.register(component, contracts);
        return this;
    }

    /**
     * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider or JAX-RS feature
     * in the {@code ResourceConfig}.
     * <p>
     * Note that registered JAX-RS features are used to initialize and configure
     * the Jersey runtime {@link InjectionManager} instance during application deployment, but are
     * otherwise ignored by server-side runtime, unless they implement also another contract
     * recognized by Jersey runtime.
     * </p>
     * <p>
     * Also note that registration of {@link Binder binder} classes is note supported. Binders
     * must be {@link #registerInstances(Object...) registered as instances}.
     * </p>
     *
     * @param classes classes to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerClasses(final Set<Class<?>> classes) {
        if (classes == null) {
            return this;
        }

        for (final Class<?> cls : classes) {
            register(cls);
        }
        return this;
    }

    /**
     * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider or JAX-RS feature
     * in the {@code ResourceConfig}.
     * <p>
     * Note that registered JAX-RS features are used to initialize and configure
     * the Jersey runtime {@link InjectionManager} instance during application deployment, but are
     * otherwise ignored by server-side runtime, unless they implement also another contract
     * recognized by Jersey runtime.
     * </p>
     * <p>
     * Also note that registration of {@link Binder binder} classes is note supported. Binders
     * must be {@link #registerInstances(Object...) registered as instances}.
     * </p>
     *
     * @param classes classes to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerClasses(final Class<?>... classes) {
        if (classes == null) {
            return this;
        }

        return registerClasses(Arrays.stream(classes).collect(Collectors.toSet()));
    }

    /**
     * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider, JAX-RS feature
     * {@link Binder Jersey Binder} instances (singletons) in the {@code ResourceConfig}.
     * <p>
     * Note that registered binders and JAX-RS features are used to initialize and configure
     * the Jersey runtime {@link InjectionManager} instance during application deployment, but are
     * otherwise ignored by server-side runtime, unless they implement also another contract
     * recognized by Jersey runtime.
     * </p>
     *
     * @param instances instances to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerInstances(final Set<Object> instances) {
        if (instances == null) {
            return this;
        }

        for (final Object instance : instances) {
            register(instance);
        }
        return this;
    }

    /**
     * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider, JAX-RS feature,
     * {@link Binder Jersey Binder} instances (singletons) in the {@code ResourceConfig}.
     * <p>
     * Note that registered binders and JAX-RS features are used to initialize and configure
     * the Jersey runtime {@link InjectionManager} instance during application deployment, but are
     * otherwise ignored by server-side runtime, unless they implement also another contract
     * recognized by Jersey runtime.
     * </p>
     *
     * @param instances instances to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerInstances(final Object... instances) {
        if (instances == null) {
            return this;
        }

        return registerInstances(Arrays.stream(instances).collect(Collectors.toSet()));
    }

    /**
     * Register new programmatic resource models in the {@code ResourceConfig}.
     *
     * @param resources resource models to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerResources(final Resource... resources) {
        if (resources == null) {
            return this;
        }

        return registerResources(Arrays.stream(resources).collect(Collectors.toSet()));
    }

    /**
     * Register new resource models in the {@code ResourceConfig}.
     *
     * @param resources resource models to register.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerResources(final Set<Resource> resources) {
        if (resources == null) {
            return this;
        }

        this.state.registerResources(resources);
        return this;
    }

    /**
     * Add a {@link ResourceFinder} to {@code ResourceConfig}.
     *
     * @param resourceFinder {@link ResourceFinder}
     * @return updated resource configuration instance.
     */
    public final ResourceConfig registerFinder(final ResourceFinder resourceFinder) {
        if (resourceFinder == null) {
            return this;
        }
        invalidateCache();

        this.state.registerFinder(resourceFinder);
        return this;
    }

    /**
     * Set the name of the application. The name is an arbitrary user defined name
     * which is used to distinguish between Jersey applications in the case that more applications
     * are deployed on the same runtime (container). The name can be used for example for purposes
     * of monitoring by JMX when name identifies to which application deployed MBeans belong to.
     * The name should be unique in the runtime.
     *
     * @param applicationName Unique application name.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig setApplicationName(final String applicationName) {
        state.setApplicationName(applicationName);
        return this;
    }

    /**
     * Set {@link ClassLoader} which will be used for resource discovery.
     *
     * @param classLoader provided {@link ClassLoader}.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig setClassLoader(final ClassLoader classLoader) {
        this.state.setClassLoader(classLoader);
        return this;
    }

    /**
     * Adds array of package names which will be used to scan for components.
     * <p/>
     * Package scanning ignores inheritance and therefore {@link Path} annotation
     * on parent classes and interfaces will be ignored.
     * <p/>
     * Packages will be scanned recursively, including all nested packages.
     *
     * @param packages array of package names.
     * @return updated resource configuration instance.
     * @see #packages(boolean, String...)
     */
    public final ResourceConfig packages(final String... packages) {
        return packages(true, packages);
    }

    /**
     * Adds array of package names which will be used to scan for components.
     * <p/>
     * Package scanning ignores an inheritance and therefore {@link Path} annotation
     * on parent classes and interfaces will be ignored.
     * <p/>
     * @param recursive defines whether any nested packages in the collection of specified
     *                  package names should be recursively scanned (value of {@code true})
     *                  as part of the package scanning or not (value of {@code false}).
     * @param packages  array of package names.
     * @return updated resource configuration instance.
     * @see #packages(String...)
     */
    public final ResourceConfig packages(final boolean recursive, final String... packages) {
        if (packages == null || packages.length == 0) {
            return this;
        }
        return registerFinder(new PackageNamesScanner(packages, recursive));
    }

    /**
     * Adds array of package names which will be used to scan for components.
     * <p/>
     * Package scanning ignores an inheritance and therefore {@link Path} annotation
     * on parent classes and interfaces will be ignored.
     * <p/>
     * @param recursive defines whether any nested packages in the collection of specified
     *                  package names should be recursively scanned (value of {@code true})
     *                  as part of the package scanning or not (value of {@code false}).
     * @param classLoader defines the classloader used for scanning the packages and loading the classes.
     * @param packages  array of package names.
     * @return updated resource configuration instance.
     * @see #packages(String...)
     */
    public final ResourceConfig packages(final boolean recursive, final ClassLoader classLoader, final String... packages) {
        if (packages == null || packages.length == 0) {
            return this;
        }
        return registerFinder(new PackageNamesScanner(classLoader, packages, recursive));
    }

    /**
     * Adds array of file and directory names to scan for components.
     * <p/>
     * Any directories in the list will be scanned recursively, including their sub-directories.
     *
     * @param files array of file and directory names.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig files(final String... files) {
        return files(true, files);
    }

    /**
     * Adds array of file and directory names to scan for components.
     *
     * @param recursive defines whether any sub-directories of the directories specified
     *                  in the collection of file names should be recursively scanned (value of {@code true})
     *                  as part of the file scanning or not (value of {@code false}).
     * @param files     array of file and directory names.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig files(final boolean recursive, final String... files) {
        if (files == null || files.length == 0) {
            return this;
        }
        return registerFinder(new FilesScanner(files, recursive));
    }

    /**
     * Invalidate cached component instances and classes.
     */
    final void invalidateCache() {
        this.cachedClasses = null;
        this.cachedClassesView = null;
        this.cachedSingletons = null;
        this.cachedSingletonsView = null;

        // Reset ResourceFinders to make sure the next package scanning is successful.
        if (resetFinders) {
            for (final ResourceFinder finder : this.state.resourceFinders) {
                finder.reset();
            }
            resetFinders = false;
        }
    }

    /**
     * Switches the ResourceConfig to read-only state.
     * <p/>
     * Called by the WrappingResourceConfig if this ResourceConfig is set as the application.
     * Also called by ApplicationHandler on WrappingResourceConfig at the point when it is going
     * to build the resource model.
     * <p/>
     * The method also sets the application name from properties if the name is not defined yer
     * and the property {@link ServerProperties#APPLICATION_NAME} is defined.
     */
    final void lock() {
        final State current = state;
        if (!(current instanceof ImmutableState)) {
            setupApplicationName();
            ExternalPropertiesConfigurationFactory.configure(state);
            state = new ImmutableState(current);
        }
    }

    @Override
    public final ServerConfig getConfiguration() {
        return this;
    }

    @Override
    public final Map<String, Object> getProperties() {
        return state.getProperties();
    }

    @Override
    public final Object getProperty(final String name) {
        return state.getProperty(name);
    }

    @Override
    public Collection<String> getPropertyNames() {
        return state.getPropertyNames();
    }

    @Override
    public final boolean isProperty(final String name) {
        return state.isProperty(name);
    }

    @Override
    public final Set<Class<?>> getClasses() {
        if (cachedClassesView == null) {
            cachedClasses = _getClasses();
            cachedClassesView = Collections.unmodifiableSet(cachedClasses);
        }
        return cachedClassesView;
    }

    @Override
    public final Set<Object> getInstances() {
        return getSingletons();
    }

    @Override
    public final Set<Object> getSingletons() {
        if (cachedSingletonsView == null) {
            cachedSingletons = _getSingletons();
            cachedSingletonsView = Collections.unmodifiableSet(cachedSingletons == null ? new HashSet<>() : cachedSingletons);
        }

        return cachedSingletonsView;
    }

    /**
     * Get the internal component bag.
     *
     * @return internal component bag.
     */
    final ComponentBag getComponentBag() {
        return state.getComponentBag();
    }

    /**
     * Configure auto-discoverables.
     *
     * @param injectionManager  injection manager to obtain auto-discoverables from.
     * @param autoDiscoverables list of registered auto discoverable components.
     */
    final void configureAutoDiscoverableProviders(InjectionManager injectionManager,
            Collection<AutoDiscoverable> autoDiscoverables) {
        state.configureAutoDiscoverableProviders(injectionManager, autoDiscoverables, false);
    }

    /**
     * Configure forced auto-discoverables.
     *
     * @param injectionManager injection manager to obtain auto-discoverables from.
     */
    final void configureForcedAutoDiscoverableProviders(InjectionManager injectionManager) {
        state.configureAutoDiscoverableProviders(injectionManager, Collections.emptyList(), true);
    }

    final void configureMetaProviders(InjectionManager injectionManager, ManagedObjectsFinalizer finalizer) {
        state.configureMetaProviders(injectionManager, finalizer);
    }

    @Override
    public RuntimeType getRuntimeType() {
        return state.getRuntimeType();
    }

    @Override
    public boolean isEnabled(final Feature feature) {
        return state.isEnabled(feature);
    }

    @Override
    public boolean isEnabled(final Class<? extends Feature> featureClass) {
        return state.isEnabled(featureClass);
    }

    @Override
    public boolean isRegistered(final Object component) {
        return state.isRegistered(component);
    }

    @Override
    public boolean isRegistered(final Class<?> componentClass) {
        return state.isRegistered(componentClass);
    }

    @Override
    public Map<Class<?>, Integer> getContracts(final Class<?> componentClass) {
        return state.getContracts(componentClass);
    }

    /**
     * Get configured resource and/or provider classes. The method is overridden
     * in a {@link WrappingResourceConfig private sub-type}.
     *
     * @return set of configured resource and/or provider classes.
     */
    Set<Class<?>> _getClasses() {
        final Set<Class<?>> result = scanClasses();
        result.addAll(state.getClasses());
        return result;
    }

    private Set<Class<?>> scanClasses() {
        final Set<Class<?>> result = new HashSet<>();

        final ResourceConfig.State _state = state;
        final Set<ResourceFinder> rfs = new HashSet<>(_state.getResourceFinders());

        // In case new entity is registered the available finders should be reset.
        resetFinders = true;

        // classes registered via configuration property
        final String[] classNames = parsePropertyValue(ServerProperties.PROVIDER_CLASSNAMES);
        if (classNames != null) {
            for (final String className : classNames) {
                try {
                    result.add(_state.getClassLoader().loadClass(className));
                } catch (final ClassNotFoundException e) {
                    LOGGER.log(Level.CONFIG, LocalizationMessages.UNABLE_TO_LOAD_CLASS(className));
                }
            }
        }

        final String[] packageNames = parsePropertyValue(ServerProperties.PROVIDER_PACKAGES);
        if (packageNames != null) {
            final Object p = getProperty(ServerProperties.PROVIDER_SCANNING_RECURSIVE);
            final boolean recursive = p == null || PropertiesHelper.isProperty(p);
            rfs.add(new PackageNamesScanner(packageNames, recursive));
        }

        final String[] classPathElements = parsePropertyValue(ServerProperties.PROVIDER_CLASSPATH);
        if (classPathElements != null) {
            rfs.add(new FilesScanner(classPathElements, true));
        }

        final AnnotationAcceptingListener parentAfl =
                AnnotationAcceptingListener.newJaxrsResourceAndProviderListener(_state.getClassLoader());

        for (final ResourceFinder resourceFinder : rfs) {
            AnnotationAcceptingListener afl = parentAfl;

            if (resourceFinder instanceof PackageNamesScanner) {
                final ClassLoader classLoader = ((PackageNamesScanner) resourceFinder).getClassloader();
                if (!getClassLoader().equals(classLoader)) {
                    afl = AnnotationAcceptingListener.newJaxrsResourceAndProviderListener(classLoader);
                }
            }

            while (resourceFinder.hasNext()) {
                final String next = resourceFinder.next();
                if (afl.accept(next)) {
                    final InputStream in = resourceFinder.open();
                    try {
                        afl.process(next, in);
                    } catch (final IOException e) {
                        LOGGER.log(Level.WARNING, LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_PROCESS(next));
                    } finally {
                        try {
                            in.close();
                        } catch (final IOException ex) {
                            LOGGER.log(Level.FINER, "Error closing resource stream.", ex);
                        }
                    }
                }
            }

            if (afl != parentAfl) {
               result.addAll(afl.getAnnotatedClasses());
            }
        }

        result.addAll(parentAfl.getAnnotatedClasses());
        return result;
    }

    private String[] parsePropertyValue(final String propertyName) {
        String[] classNames = null;
        final Object o = state.getProperties().get(propertyName);
        if (o != null) {
            if (o instanceof String) {
                classNames = Tokenizer.tokenize((String) o);
            } else if (o instanceof String[]) {
                classNames = Tokenizer.tokenize((String[]) o);
            }
        }
        return classNames;
    }

    /**
     * Return classes which were registered by the user and not found by class path scanning (or any other scanning).
     *
     * @return Set of classes registered by the user.
     */
    Set<Class<?>> getRegisteredClasses() {
        return state.getComponentBag().getRegistrations();
    }

    /**
     * Get configured resource and/or provider instances. The method is overridden
     * in a {@link WrappingResourceConfig private sub-type}.
     *
     * @return set of configured resource and/or provider instances.
     */
    Set<Object> _getSingletons() {
        final Set<Object> result = new HashSet<>();
        result.addAll(state.getInstances());
        return result;
    }

    @Override
    public final Set<Resource> getResources() {
        return state.getResources();
    }

    /**
     * Get resource and provider class loader.
     *
     * @return class loader to be used when looking up the resource classes and
     * providers.
     */
    public final ClassLoader getClassLoader() {
        return state.getClassLoader();
    }

    /**
     * Returns JAX-RS application corresponding with this ResourceConfig.
     *
     * @return JAX-RS application corresponding with this ResourceConfig.
     */
    public final Application getApplication() {
        return _getApplication();
    }

    /**
     * Returns encoded value of {@link ApplicationPath} annotation of the Application corresponding
     * with this ResourceConfig or {@code null} when the annotation is not present.
     *
     * @return Returns encoded value of {@link ApplicationPath} annotation of the Application
     * corresponding with this ResourceConfig.
     */
    public final String getApplicationPath() {
        final Application application;
        if (ResourceConfig.class.isInstance(_getApplication())) {
              final Application unwrap = unwrapCustomRootApplication((ResourceConfig) _getApplication());
              application = unwrap != null ? unwrap : _getApplication();
        } else {
            application = _getApplication();
        }
        final ApplicationPath appPath = application.getClass().getAnnotation(ApplicationPath.class);
        final String value;
        if (appPath != null && !appPath.value().isEmpty() && !appPath.value().trim().equals("/")) {
            final String val = appPath.value().trim();
            value = UriComponent.encode(val.startsWith("/") ? val.substring(1) : val, UriComponent.Type.PATH);
        } else {
            value = null;
        }
        return value;
    }

    /**
     * Allows overriding the {@link #getApplication()} method functionality in {@link WrappingResourceConfig}.
     *
     * @return JAX-RS application corresponding with this ResourceConfig.
     */
    Application _getApplication() {
        return this;
    }

    /**
     * Get the name of the Jersey application.
     *
     * @return Name of the application.
     * @see #setApplicationName(String)
     */
    public String getApplicationName() {
        return state.getApplicationName();
    }

    /**
     * Method used by ApplicationHandler to retrieve application class
     * (this method is overridden by {@link WrappingResourceConfig}).
     *
     * @return application class
     */
    Class<? extends Application> getApplicationClass() {
        return null;
    }

    /**
     * This method is used by ApplicationHandler to set application instance to the resource config (should
     * always be called on WrappingResourceConfig instance, never on plain instances of ResourceConfig
     * unless we have a bug in the code).
     *
     * @param app JAX-RS application
     * @return this ResourceConfig instance (for convenience)
     */
    final ResourceConfig setApplication(final Application app) {
        return _setApplication(app);
    }

    /**
     * Allows overriding the setApplication() method functionality in WrappingResourceConfig.
     *
     * @param app application to be set for this ResourceConfig
     * @return this resource config instance
     */
    ResourceConfig _setApplication(final Application app) {
        throw new UnsupportedOperationException();
    }

    private static class WrappingResourceConfig extends ResourceConfig {

        private Application application;
        private Class<? extends Application> applicationClass;
        private final Set<Class<?>> defaultClasses = new HashSet<>();

        public WrappingResourceConfig(
                final Application application, final Class<? extends Application> applicationClass,
                final Set<Class<?>> defaultClasses) {

            if (application == null && applicationClass == null) {
                throw new IllegalArgumentException(LocalizationMessages.RESOURCE_CONFIG_ERROR_NULL_APPLICATIONCLASS());
            }
            this.application = application;
            this.applicationClass = applicationClass;
            if (defaultClasses != null) {
                this.defaultClasses.addAll(defaultClasses);
            }
            mergeApplications(application);
        }

        /**
         * Set the {@link jakarta.ws.rs.core.Application JAX-RS Application instance}
         * in the {@code ResourceConfig}.
         * <p/>
         * This method is used by the {@link org.glassfish.jersey.server.ApplicationHandler} in case this resource
         * configuration instance was created with application class rather than application instance.
         *
         * @param application JAX-RS Application instance.
         * @return updated resource configuration instance.
         */
        @Override
        ResourceConfig _setApplication(final Application application) {
            this.application = application;
            this.applicationClass = null;
            mergeApplications(application);
            return this;
        }

        /**
         * Get the original underlying JAX-RS {@link Application} instance used to
         * initialize the resource configuration instance.
         *
         * @return JAX-RS application instance.
         */
        @Override
        Application _getApplication() {
            return application;
        }

        /**
         * Get the original JAX-RS {@link Application} class provided it was not
         * instantiated yet. A {@code null} is returned in case the class has been
         * instantiated already or was not configured at all.
         * <p>
         * This class will be used to initialize the resource configuration instance.
         * If there is no JAX-RS application class set, or if the class has been
         * instantiated already, the method will return {@code null}.
         * </p>
         *
         * @return original JAX-RS application class or {@code null} if there is no
         * such class configured or if the class has been already instantiated.
         */
        @Override
        Class<? extends Application> getApplicationClass() {
            return applicationClass;
        }

        /**
         * Merges fields (e.g. custom binders, properties) of the given application with this application.
         * <p>
         * The merging should be done because of the possibility of reloading this {@code ResourceConfig} in a container
         * so this resource config should know about custom binders and properties of the underlying application to ensure
         * the reload process will complete successfully.
         * </p>
         *
         * @param application the application which fields should be merged with this application.
         * @see org.glassfish.jersey.server.spi.Container#reload()
         * @see org.glassfish.jersey.server.spi.Container#reload(ResourceConfig)
         */
        private void mergeApplications(final Application application) {
            if (application instanceof ResourceConfig) {
                // Merge custom binders.
                final ResourceConfig rc = (ResourceConfig) application;

                // Merge resources
                super.registerResources(rc.getResources());

                // properties set on the wrapping resource config take precedence
                // (as those are retrieved from the web.xml, for example)
                rc.invalidateCache();
                rc.addProperties(super.getProperties());
                super.addProperties(rc.getProperties());
                super.setApplicationName(rc.getApplicationName());
                super.setClassLoader(rc.getClassLoader());

                rc.lock();
            } else if (application != null) {
                super.addProperties(application.getProperties());
            }
        }

        @Override
        Set<Class<?>> _getClasses() {
            final Set<Class<?>> result = new HashSet<>();
            final Set<Class<?>> applicationClasses = application.getClasses();
            result.addAll(applicationClasses == null ? new HashSet<Class<?>>() : applicationClasses);
            if (result.isEmpty() && getSingletons().isEmpty()) {
                result.addAll(defaultClasses);
            }

            // if the application is not an instance of ResourceConfig, handle scanning triggered via properties
            if (!(application instanceof ResourceConfig)) {
                result.addAll(super._getClasses());
            }
            return result;
        }

        @Override
        Set<Object> _getSingletons() {
            return application.getSingletons();
        }
    }

    /**
     * Create runtime configuration initialized from a given deploy-time JAX-RS/Jersey
     * application configuration.
     *
     * @param application deploy-time JAX-RS/Jersey application configuration.
     * @return initialized run-time resource config.
     */
    static ResourceConfig createRuntimeConfig(final Application application) {
        return (application instanceof ResourceConfig)
                ? new RuntimeConfig((ResourceConfig) application) : new RuntimeConfig(application);
    }

    private static class RuntimeConfig extends ResourceConfig {

        private final Set<Class<?>> originalRegistrations;
        private final Application application;

        private RuntimeConfig(final ResourceConfig original) {
            super(original);

            this.application = original;

            final Application customRootApp = ResourceConfig.unwrapCustomRootApplication(original);

            final Set<Object> rootSingletons = customRootApp != null ? registerComponentsOf(customRootApp) : null;

            originalRegistrations = Collections.newSetFromMap(new IdentityHashMap<>());
            originalRegistrations.addAll(super.getRegisteredClasses());

            // Do not call the same Application#getSingletons twice
            final Set<Object> origSingletons = customRootApp != null ? rootSingletons : original.getSingletons();

            // Register externally provided instances.
            final Set<Object> externalInstances =
                    origSingletons.stream()
                            .filter(external -> !originalRegistrations.contains(external.getClass()))
                            .collect(Collectors.toSet());

            registerInstances(externalInstances);

            // Register externally provided classes.
            final Set<Class<?>> externalClasses = original.getClasses().stream()
                                                          .filter(external -> !originalRegistrations.contains(external))
                                                          .collect(Collectors.toSet());
            registerClasses(externalClasses);
        }

        private Set<Object> registerComponentsOf(final Application application) {
            return Errors.process(new Producer<Set<Object>>() {
                @Override
                public Set<Object> call() {
                    // First register instances that should take precedence over classes
                    // in case of duplicate registrations
                    final Set<Object> singletons = application.getSingletons();
                    if (singletons != null) {
                        registerInstances(
                                singletons.stream()
                                          .filter(input -> {
                                              if (input == null) {
                                                  Errors.warning(application,
                                                                 LocalizationMessages.NON_INSTANTIABLE_COMPONENT(null));
                                              }
                                              return input != null;
                                          })
                                          .collect(Collectors.toSet()));
                    }

                    final Set<Class<?>> classes = application.getClasses();
                    if (classes != null) {
                        registerClasses(classes.stream()
                                               .filter(input -> {
                                                   if (input == null) {
                                                       Errors.warning(application,
                                                                      LocalizationMessages.NON_INSTANTIABLE_COMPONENT(null));
                                                   }
                                                   return input != null;
                                               })
                                               .collect(Collectors.toSet()));
                    }
                    return singletons;
                }
            });

        }

        private RuntimeConfig(final Application application) {
            super();

            this.application = application;

            if (application != null) {
                registerComponentsOf(application);

                // Copy all available properties.
                addProperties(application.getProperties());
            }

            originalRegistrations = super.getRegisteredClasses();
        }

        @Override
        Set<Class<?>> _getClasses() {
            // Get only a read-only classes cached in internal state.
            return super.state.getClasses();
        }

        @Override
        Set<Object> _getSingletons() {
            // Get only a read-only classes cached in internal state.
            return super.state.getInstances();
        }

        @Override
        Set<Class<?>> getRegisteredClasses() {
            return originalRegistrations;
        }

        @Override
        Application _getApplication() {
            return application;
        }
    }

    private static Application unwrapCustomRootApplication(ResourceConfig resourceConfig) {
        Application app = null;
        while (resourceConfig != null) {
            app = resourceConfig.getApplication();
            if (app == resourceConfig) {
                // resource config is the root application - return null
                return null;
            } else if (app instanceof ResourceConfig) {
                resourceConfig = (ResourceConfig) app;
            } else {
                break;
            }
        }
        return app;
    }

    /**
     * Get the most internal wrapped {@link Application application} class.
     * <p>
     * This method is similar to {@link ResourceConfig#getApplication()} except if provided application was
     * created by wrapping multiple {@code ResourceConfig} instances, this method will return the original (inner-most)
     * JAX-RS {@code Application} sub-class rather than a potentially intermediate {@code ResourceConfig} wrapper.
     * </p>
     *
     * @param application application that is potentially wrapped.
     * @return the original, inner-most {@link Application} subclass. May return the same instance directly,
     * in case the supplied {@code application} instance is not a wrapper {@code ResourceConfig} instance.
     */
    static Application unwrapApplication(Application application) {
        while (application instanceof ResourceConfig) {
            final Application wrappedApplication = ((ResourceConfig) application).getApplication();
            if (wrappedApplication == application) {
                break;
            }
            application = wrappedApplication;
        }
        return application;
    }

    private void setupApplicationName() {
        final String appName = ServerProperties.getValue(getProperties(), ServerProperties.APPLICATION_NAME, null, String.class);
        if (appName != null && getApplicationName() == null) {
            setApplicationName(appName);
        }
    }

}
