/*
 * Copyright (c) 2021, 2022, 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.
 *
 * 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.
 */

import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Requires;
import java.util.Set;

import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotSame;

/**
 * @test
 * @bug 8275509 8290041
 * @summary Tests the ModuleDescriptor.hashCode()
 * @run testng ModuleDescriptorHashCodeTest
 * @run testng/othervm -Xshare:off ModuleDescriptorHashCodeTest
 */
public class ModuleDescriptorHashCodeTest {

    /**
     * Verifies that the ModuleDescriptor.hashCode() returned by a boot layer module is
     * the same as that returned by a ModuleDescriptor constructed from the ModuleDescriptor.Builder
     * for the same module.
     */
    @Test
    public void testBootModuleDescriptor() throws Exception {
        Set<Module> bootModules = ModuleLayer.boot().modules();
        for (Module bootModule : bootModules) {
            System.out.println("Testing module descriptor of boot module " + bootModule);
            ModuleDescriptor bootMD = bootModule.getDescriptor();
            ModuleDescriptor mdFromBuilder = fromModuleInfoClass(bootModule);
            // verify that this object is indeed a different object instance than the boot module descriptor
            // to prevent any artificial passing of the test
            assertNotSame(mdFromBuilder, bootMD, "ModuleDescriptor loaded from boot layer and " +
                    "one created from module-info.class unexpectedly returned the same instance");
            assertEquals(mdFromBuilder.hashCode(), bootMD.hashCode(),
                    "Unexpected ModuleDescriptor.hashCode() for " + mdFromBuilder);
            assertEquals(mdFromBuilder.compareTo(bootMD), 0,
                    "Unexpected ModuleDescriptor.compareTo() for " + mdFromBuilder);
        }
    }

    /**
     * Verifies that two "equal" module descriptors which only differ in the order of
     * {@link ModuleDescriptor.Opens.Modifier opens modifiers}, that were used to construct the
     * descriptors, have the same hashcode.
     */
    @Test
    public void testOpensModifiersOrdering() throws Exception {
        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Opens.Modifier> mods1 = Set.of(Opens.Modifier.SYNTHETIC, Opens.Modifier.MANDATED);
        final ModuleDescriptor desc1 = createModuleDescriptor(mods1, null, null);

        // create the same module descriptor again and this time just change the order of the
        // "opens" modifiers' Set.

        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Opens.Modifier> mods2 = Set.of(Opens.Modifier.MANDATED, Opens.Modifier.SYNTHETIC);
        final ModuleDescriptor desc2 = createModuleDescriptor(mods2, null, null);

        // basic verification of the modifiers themselves before we check the module descriptors
        assertEquals(mods1, mods2, "Modifiers were expected to be equal");

        // now verify the module descriptors
        assertEquals(desc1, desc2, "Module descriptors were expected to be equal");
        assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to return" +
                " 0 for module descriptors that are equal");
        System.out.println(desc1 + " hashcode = " + desc1.hashCode());
        System.out.println(desc2 + " hashcode = " + desc2.hashCode());
        assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor hashcodes" +
                " were expected to be equal");
    }

    /**
     * Verifies that two "equal" module descriptors which only differ in the order of
     * {@link ModuleDescriptor.Exports.Modifier exports modifiers}, that were used to construct the
     * descriptors, have the same hashcode.
     */
    @Test
    public void testExportsModifiersOrdering() throws Exception {
        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Exports.Modifier> mods1 = Set.of(Exports.Modifier.SYNTHETIC, Exports.Modifier.MANDATED);
        final ModuleDescriptor desc1 = createModuleDescriptor(null, null, mods1);

        // create the same module descriptor again and this time just change the order of the
        // "exports" modifiers' Set.

        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Exports.Modifier> mods2 = Set.of(Exports.Modifier.MANDATED, Exports.Modifier.SYNTHETIC);
        final ModuleDescriptor desc2 = createModuleDescriptor(null, null, mods2);

        // basic verification of the modifiers themselves before we check the module descriptors
        assertEquals(mods1, mods2, "Modifiers were expected to be equal");

        // now verify the module descriptors
        assertEquals(desc1, desc2, "Module descriptors were expected to be equal");
        assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to return" +
                " 0 for module descriptors that are equal");
        System.out.println(desc1 + " hashcode = " + desc1.hashCode());
        System.out.println(desc2 + " hashcode = " + desc2.hashCode());
        assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor hashcodes" +
                " were expected to be equal");
    }

    /**
     * Verifies that two "equal" module descriptors which only differ in the order of
     * {@link ModuleDescriptor.Requires.Modifier requires modifiers}, that were used to construct the
     * descriptors, have the same hashcode.
     */
    @Test
    public void testRequiresModifiersOrdering() throws Exception {
        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Requires.Modifier> mods1 = Set.of(Requires.Modifier.SYNTHETIC, Requires.Modifier.MANDATED);
        final ModuleDescriptor desc1 = createModuleDescriptor(null, mods1, null);

        // create the same module descriptor again and this time just change the order of the
        // "exports" modifiers' Set.

        // important to use Set.of() (i.e. backed by immutable set) to reproduce the issue
        final Set<Requires.Modifier> mods2 = Set.of(Requires.Modifier.MANDATED, Requires.Modifier.SYNTHETIC);
        final ModuleDescriptor desc2 = createModuleDescriptor(null, mods2, null);

        // basic verification of the modifiers themselves before we check the module descriptors
        assertEquals(mods1, mods2, "Modifiers were expected to be equal");

        // now verify the module descriptors
        assertEquals(desc1, desc2, "Module descriptors were expected to be equal");
        assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to return" +
                " 0 for module descriptors that are equal");
        System.out.println(desc1 + " hashcode = " + desc1.hashCode());
        System.out.println(desc2 + " hashcode = " + desc2.hashCode());
        assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor hashcodes" +
                " were expected to be equal");
    }

    // Returns a ModuleDescriptor parsed out of the module-info.class of the passed Module
    private static ModuleDescriptor fromModuleInfoClass(Module module) throws IOException {
        try (InputStream moduleInfo = module.getResourceAsStream("module-info.class")) {
            if (moduleInfo == null) {
                throw new RuntimeException("Could not locate module-info.class in " + module);
            }
            // internally calls ModuleDescriptor.Builder
            return ModuleDescriptor.read(moduleInfo);
        }
    }

    // creates a module descriptor with passed (optional) opens/exports/requires modifiers
    private static ModuleDescriptor createModuleDescriptor(
            Set<Opens.Modifier> opensModifiers,
            Set<Requires.Modifier> reqsModifiers,
            Set<Exports.Modifier> expsModifiers) {

        final ModuleDescriptor.Builder builder = ModuleDescriptor.newModule("foobar");
        if (opensModifiers != null) {
            builder.opens(opensModifiers, "a.p1", Set.of("a.m1"));
        }
        if (reqsModifiers != null) {
            builder.requires(reqsModifiers, "a.m2");
        }
        if (expsModifiers != null) {
            builder.exports(expsModifiers, "a.b.c", Set.of("a.m3"));
        }
        return builder.build();
    }
}