/*
 * 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.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.objectweb.asm;

/**
 * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and
 * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS).
 *
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25">JVMS
 *     4.7.25</a>
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.26">JVMS
 *     4.7.26</a>
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.27">JVMS
 *     4.7.27</a>
 * @author Remi Forax
 * @author Eric Bruneton
 */
final class ModuleWriter extends ModuleVisitor {

    /** Where the constants used in this AnnotationWriter must be stored. */
    private final SymbolTable symbolTable;

    /** The module_name_index field of the JVMS Module attribute. */
    private final int moduleNameIndex;

    /** The module_flags field of the JVMS Module attribute. */
    private final int moduleFlags;

    /** The module_version_index field of the JVMS Module attribute. */
    private final int moduleVersionIndex;

    /** The requires_count field of the JVMS Module attribute. */
    private int requiresCount;

    /** The binary content of the 'requires' array of the JVMS Module attribute. */
    private final ByteVector requires;

    /** The exports_count field of the JVMS Module attribute. */
    private int exportsCount;

    /** The binary content of the 'exports' array of the JVMS Module attribute. */
    private final ByteVector exports;

    /** The opens_count field of the JVMS Module attribute. */
    private int opensCount;

    /** The binary content of the 'opens' array of the JVMS Module attribute. */
    private final ByteVector opens;

    /** The uses_count field of the JVMS Module attribute. */
    private int usesCount;

    /** The binary content of the 'uses_index' array of the JVMS Module attribute. */
    private final ByteVector usesIndex;

    /** The provides_count field of the JVMS Module attribute. */
    private int providesCount;

    /** The binary content of the 'provides' array of the JVMS Module attribute. */
    private final ByteVector provides;

    /** The provides_count field of the JVMS ModulePackages attribute. */
    private int packageCount;

    /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */
    private final ByteVector packageIndex;

    /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */
    private int mainClassIndex;

    ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
        super(/* latest api = */ Opcodes.ASM9);
        this.symbolTable = symbolTable;
        this.moduleNameIndex = name;
        this.moduleFlags = access;
        this.moduleVersionIndex = version;
        this.requires = new ByteVector();
        this.exports = new ByteVector();
        this.opens = new ByteVector();
        this.usesIndex = new ByteVector();
        this.provides = new ByteVector();
        this.packageIndex = new ByteVector();
    }

    @Override
    public void visitMainClass(final String mainClass) {
        this.mainClassIndex = symbolTable.addConstantClass(mainClass).index;
    }

    @Override
    public void visitPackage(final String packaze) {
        packageIndex.putShort(symbolTable.addConstantPackage(packaze).index);
        packageCount++;
    }

    @Override
    public void visitRequire(final String module, final int access, final String version) {
        requires
                .putShort(symbolTable.addConstantModule(module).index)
                .putShort(access)
                .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
        requiresCount++;
    }

    @Override
    public void visitExport(final String packaze, final int access, final String... modules) {
        exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
        if (modules == null) {
            exports.putShort(0);
        } else {
            exports.putShort(modules.length);
            for (String module : modules) {
                exports.putShort(symbolTable.addConstantModule(module).index);
            }
        }
        exportsCount++;
    }

    @Override
    public void visitOpen(final String packaze, final int access, final String... modules) {
        opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
        if (modules == null) {
            opens.putShort(0);
        } else {
            opens.putShort(modules.length);
            for (String module : modules) {
                opens.putShort(symbolTable.addConstantModule(module).index);
            }
        }
        opensCount++;
    }

    @Override
    public void visitUse(final String service) {
        usesIndex.putShort(symbolTable.addConstantClass(service).index);
        usesCount++;
    }

    @Override
    public void visitProvide(final String service, final String... providers) {
        provides.putShort(symbolTable.addConstantClass(service).index);
        provides.putShort(providers.length);
        for (String provider : providers) {
            provides.putShort(symbolTable.addConstantClass(provider).index);
        }
        providesCount++;
    }

    @Override
    public void visitEnd() {
        // Nothing to do.
    }

    /**
      * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this
      * ModuleWriter.
      *
      * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3).
      */
    int getAttributeCount() {
        return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0);
    }

    /**
      * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this
      * ModuleWriter. Also add the names of these attributes in the constant pool.
      *
      * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes.
      */
    int computeAttributesSize() {
        symbolTable.addConstantUtf8(Constants.MODULE);
        // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
        int size =
                22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
        if (packageCount > 0) {
            symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES);
            // 6 attribute header bytes, and 2 bytes for package_count.
            size += 8 + packageIndex.length;
        }
        if (mainClassIndex > 0) {
            symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS);
            // 6 attribute header bytes, and 2 bytes for main_class_index.
            size += 8;
        }
        return size;
    }

    /**
      * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter
      * in the given ByteVector.
      *
      * @param output where the attributes must be put.
      */
    void putAttributes(final ByteVector output) {
        // 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
        int moduleAttributeLength =
                16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
        output
                .putShort(symbolTable.addConstantUtf8(Constants.MODULE))
                .putInt(moduleAttributeLength)
                .putShort(moduleNameIndex)
                .putShort(moduleFlags)
                .putShort(moduleVersionIndex)
                .putShort(requiresCount)
                .putByteArray(requires.data, 0, requires.length)
                .putShort(exportsCount)
                .putByteArray(exports.data, 0, exports.length)
                .putShort(opensCount)
                .putByteArray(opens.data, 0, opens.length)
                .putShort(usesCount)
                .putByteArray(usesIndex.data, 0, usesIndex.length)
                .putShort(providesCount)
                .putByteArray(provides.data, 0, provides.length);
        if (packageCount > 0) {
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES))
                    .putInt(2 + packageIndex.length)
                    .putShort(packageCount)
                    .putByteArray(packageIndex.data, 0, packageIndex.length);
        }
        if (mainClassIndex > 0) {
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS))
                    .putInt(2)
                    .putShort(mainClassIndex);
        }
    }
}
