/*
 * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.lang.classfile.attribute;

import java.lang.classfile.Attribute;
import java.lang.classfile.AttributeMapper;
import java.lang.classfile.Attributes;
import java.lang.classfile.ClassElement;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.classfile.constantpool.ModuleEntry;
import java.lang.classfile.constantpool.Utf8Entry;
import java.lang.constant.ClassDesc;
import java.lang.constant.ModuleDesc;
import java.lang.constant.PackageDesc;
import java.lang.invoke.MethodHandles;
import java.lang.module.ModuleDescriptor;
import java.lang.reflect.AccessFlag;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;

import jdk.internal.classfile.impl.BoundAttribute;
import jdk.internal.classfile.impl.ModuleAttributeBuilderImpl;
import jdk.internal.classfile.impl.UnboundAttribute;
import jdk.internal.classfile.impl.Util;

/**
 * Models the {@link Attributes#module() Module} attribute (JVMS {@jvms 4.7.25}),
 * which always appears on classes that {@linkplain ClassModel#isModuleInfo()
 * represent} module descriptors.
 * <p>
 * This attribute only appears on classes, and does not permit {@linkplain
 * AttributeMapper#allowMultiple multiple instances} in a class.  It has a
 * data dependency on the {@linkplain AttributeMapper.AttributeStability#CP_REFS
 * constant pool}.
 * <p>
 * The attribute was introduced in the Java SE Platform version 9, major version
 * {@value ClassFile#JAVA_9_VERSION}.
 *
 * @see Attributes#module()
 * @see ModuleDescriptor
 * @jvms 4.7.25 The {@code Module} Attribute
 * @since 24
 */
public sealed interface ModuleAttribute
        extends Attribute<ModuleAttribute>, ClassElement
        permits BoundAttribute.BoundModuleAttribute, UnboundAttribute.UnboundModuleAttribute {

    /**
     * {@return the name of the module}
     *
     * @see ModuleDescriptor#name()
     */
    ModuleEntry moduleName();

    /**
     * {@return the module flags of the module, as a bit mask}  It is a {@link
     * java.lang.classfile##u2 u2} value.
     *
     * @see ModuleDescriptor#modifiers()
     * @see AccessFlag.Location#MODULE
     */
    int moduleFlagsMask();

    /**
     * {@return the module flags of the module, as a set of enum constants}
     *
     * @throws IllegalArgumentException if the flags mask has any undefined bit set
     * @see ModuleDescriptor#accessFlags()
     * @see AccessFlag.Location#MODULE
     */
    default Set<AccessFlag> moduleFlags() {
        return AccessFlag.maskToAccessFlags(moduleFlagsMask(), AccessFlag.Location.MODULE);
    }

    /**
     * Tests presence of module flag.
     *
     * @param flag the module flag
     * @return true if the flag is set
     * @see AccessFlag.Location#MODULE
     */
    default boolean has(AccessFlag flag) {
        return Util.has(AccessFlag.Location.MODULE, moduleFlagsMask(), flag);
    }

    /**
     * {@return the version of the module, if present}
     *
     * @see ModuleDescriptor#version()
     */
    Optional<Utf8Entry> moduleVersion();

    /**
     * {@return the modules required by this module}
     *
     * @see ModuleDescriptor#requires()
     */
    List<ModuleRequireInfo> requires();

    /**
     * {@return the packages exported by this module}
     *
     * @see ModuleDescriptor#packages()
     */
    List<ModuleExportInfo> exports();

    /**
     * {@return the packages opened by this module}
     *
     * @apiNote
     * Opening a package to another module allows that other module to gain
     * the same full privilege access as members in this module.  See {@link
     * MethodHandles#privateLookupIn} for more details.
     *
     * @see ModuleDescriptor#opens()
     */
    List<ModuleOpenInfo> opens();

    /**
     * {@return the services used by this module}  Services may be discovered via
     * {@link ServiceLoader}.
     *
     * @see ModuleDescriptor#uses()
     */
    List<ClassEntry> uses();

    /**
     * {@return the service implementations provided by this module}
     *
     * @see ModuleDescriptor#provides()
     */
    List<ModuleProvideInfo> provides();

    /**
     * {@return a {@code Module} attribute}
     *
     * @param moduleName the module name
     * @param moduleFlags the module flags
     * @param moduleVersion the module version, may be {@code null}
     * @param requires the required packages
     * @param exports the exported packages
     * @param opens the opened packages
     * @param uses the consumed services
     * @param provides the provided services
     * @throws IllegalArgumentException if {@code moduleFlags} is not {@link
     *         java.lang.classfile##u2 u2} or any of the collections has a size
     *         over the limit of {@link java.lang.classfile##u2 u2}
     */
    static ModuleAttribute of(ModuleEntry moduleName, int moduleFlags,
                              Utf8Entry moduleVersion,
                              Collection<ModuleRequireInfo> requires,
                              Collection<ModuleExportInfo> exports,
                              Collection<ModuleOpenInfo> opens,
                              Collection<ClassEntry> uses,
                              Collection<ModuleProvideInfo> provides) {
        return new UnboundAttribute.UnboundModuleAttribute(moduleName, moduleFlags, moduleVersion, requires, exports, opens, uses, provides);
    }

    /**
     * {@return a {@code Module} attribute}
     *
     * @param moduleName the module name
     * @param attrHandler a handler that receives a {@link ModuleAttributeBuilder}
     * @throws IllegalArgumentException if the information from the handler exceeds
     *         the {@code class} file format limit, such as a list with size
     *         over the limit of {@link java.lang.classfile##u2 u2}
     */
    static ModuleAttribute of(ModuleDesc moduleName,
                              Consumer<ModuleAttributeBuilder> attrHandler) {
        var mb = new ModuleAttributeBuilderImpl(moduleName);
        attrHandler.accept(mb);
        return mb.build();
    }

    /**
     * {@return a {@code Module} attribute}
     *
     * @param moduleName the module name
     * @param attrHandler a handler that receives a {@link ModuleAttributeBuilder}
     */
    static ModuleAttribute of(ModuleEntry moduleName,
                              Consumer<ModuleAttributeBuilder> attrHandler) {
        var mb = new ModuleAttributeBuilderImpl(moduleName);
        attrHandler.accept(mb);
        return mb.build();
    }

    /**
     * A builder for {@link ModuleAttribute Module} attributes.
     *
     * @see ModuleDescriptor.Builder
     * @jvms 4.7.25 The {@code Module} Attribute
     * @since 24
     */
    public sealed interface ModuleAttributeBuilder
            permits ModuleAttributeBuilderImpl {

        /**
         * Sets the module name.
         *
         * @param moduleName the module name
         * @return this builder
         */
        ModuleAttributeBuilder moduleName(ModuleDesc moduleName);

        /**
         * Sets the module flags.
         *
         * @param flagsMask the module flags
         * @return this builder
         * @throws IllegalArgumentException if {@code flagsMask} is not {@link
         *         java.lang.classfile##u2 u2}
         */
        ModuleAttributeBuilder moduleFlags(int flagsMask);

        /**
         * Sets the module flags.
         *
         * @param moduleFlags the module flags
         * @return this builder
         * @throws IllegalArgumentException if any flag cannot be applied to the
         *         {@link AccessFlag.Location#MODULE} location
         */
        default ModuleAttributeBuilder moduleFlags(AccessFlag... moduleFlags) {
            return moduleFlags(Util.flagsToBits(AccessFlag.Location.MODULE, moduleFlags));
        }

        /**
         * Sets the module version, which may be {@code null}.
         *
         * @param version the module version, may be {@code null}
         * @return this builder
         */
        ModuleAttributeBuilder moduleVersion(String version);

        /**
         * Adds a module requirement.
         *
         * @param module the required module
         * @param requiresFlagsMask the requires flags
         * @param version the required module version, may be {@code null}
         * @return this builder
         */
        ModuleAttributeBuilder requires(ModuleDesc module, int requiresFlagsMask, String version);

        /**
         * Adds a module requirement.
         *
         * @param module the required module
         * @param requiresFlags the requires flags
         * @param version the required module version, may be {@code null}
         * @return this builder
         * @throws IllegalArgumentException if any flag cannot be applied to the
         *         {@link AccessFlag.Location#MODULE_REQUIRES} location
         */
        default ModuleAttributeBuilder requires(ModuleDesc module, Collection<AccessFlag> requiresFlags, String version) {
            return requires(module, Util.flagsToBits(AccessFlag.Location.MODULE_REQUIRES, requiresFlags), version);
        }

        /**
         * Adds module requirement.
         *
         * @param requires the module require info
         * @return this builder
         */
        ModuleAttributeBuilder requires(ModuleRequireInfo requires);

        /**
         * Adds an exported package.
         *
         * @param pkge the exported package
         * @param exportsFlagsMask the export flags
         * @param exportsToModules the modules to export to, or empty for an unqualified export
         * @return this builder
         * @throws IllegalArgumentException if the number of modules exceeds
         *         the limit of {@link java.lang.classfile##u2 u2}
         */
        ModuleAttributeBuilder exports(PackageDesc pkge, int exportsFlagsMask, ModuleDesc... exportsToModules);

        /**
         * Adds an exported package.
         *
         * @param pkge the exported package
         * @param exportsFlags the export flags
         * @param exportsToModules the modules to export to, or empty for an unqualified export
         * @return this builder
         * @throws IllegalArgumentException if any flag cannot be applied to the
         *         {@link AccessFlag.Location#MODULE_EXPORTS} location or the
         *         number of modules exceeds the limit of {@link
         *         java.lang.classfile##u2 u2}
         */
        default ModuleAttributeBuilder exports(PackageDesc pkge, Collection<AccessFlag> exportsFlags, ModuleDesc... exportsToModules) {
            return exports(pkge, Util.flagsToBits(AccessFlag.Location.MODULE_EXPORTS, exportsFlags), exportsToModules);
        }

        /**
         * Adds an exported package.
         *
         * @param exports the module export info
         * @return this builder
         */
        ModuleAttributeBuilder exports(ModuleExportInfo exports);

        /**
         * Opens a package.
         *
         * @apiNote
         * Opening a package to another module allows that other module to gain
         * the same full privilege access as members in this module.  See {@link
         * MethodHandles#privateLookupIn} for more details.
         *
         * @param pkge the opened package
         * @param opensFlagsMask the open package flags
         * @param opensToModules the modules to open to, or empty for an unqualified open
         * @return this builder
         * @throws IllegalArgumentException if the number of modules exceeds the
         *         limit of {@link java.lang.classfile##u2 u2}
         */
        ModuleAttributeBuilder opens(PackageDesc pkge, int opensFlagsMask, ModuleDesc... opensToModules);

        /**
         * Opens a package.
         *
         * @apiNote
         * Opening a package to another module allows that other module to gain
         * the same full privilege access as members in this module.  See {@link
         * MethodHandles#privateLookupIn} for more details.
         *
         * @param pkge the opened package
         * @param opensFlags the open package flags
         * @param opensToModules the modules to open to, or empty for an unqualified open
         * @return this builder
         * @throws IllegalArgumentException if any flag cannot be applied to the
         *         {@link AccessFlag.Location#MODULE_OPENS} location, or if the
         *         number of modules exceeds the limit of {@link
         *         java.lang.classfile##u2 u2}
         */
        default ModuleAttributeBuilder opens(PackageDesc pkge, Collection<AccessFlag> opensFlags, ModuleDesc... opensToModules) {
            return opens(pkge, Util.flagsToBits(AccessFlag.Location.MODULE_OPENS, opensFlags), opensToModules);
        }

        /**
         * Opens a package.
         *
         * @apiNote
         * Opening a package to another module allows that other module to gain
         * the same full privilege access as members in this module.  See {@link
         * MethodHandles#privateLookupIn} for more details.
         *
         * @param opens the module open info
         * @return this builder
         */
        ModuleAttributeBuilder opens(ModuleOpenInfo opens);

        /**
         * Declares use of a service.
         *
         * @param service the service class used
         * @return this builder
         * @throws IllegalArgumentException if {@code service} represents a primitive type
         */
        ModuleAttributeBuilder uses(ClassDesc service);

        /**
         * Declares use of a service.
         *
         * @param uses the service class used
         * @return this builder
         */
        ModuleAttributeBuilder uses(ClassEntry uses);

        /**
         * Declares provision of a service.
         *
         * @param service the service class provided
         * @param implClasses the implementation classes
         * @return this builder
         * @throws IllegalArgumentException if {@code service} or any of the
         *         {@code implClasses} represents a primitive type, or the
         *         number of implementations exceeds the limit of {@link
         *         java.lang.classfile##u2 u2}
         */
        ModuleAttributeBuilder provides(ClassDesc service, ClassDesc... implClasses);

        /**
         * Declares provision of a service.
         *
         * @param provides the module provides info
         * @return this builder
         */
        ModuleAttributeBuilder provides(ModuleProvideInfo provides);
    }
}
