/*
 * Copyright (c) 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.
 *
 * 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 org.openjdk.bench.java.lang.runtime;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

/// Tests the generated equals and hashCode for records.
/// There are 4 types of methods:
///  - distinct: distinct sites for type profiling
///  - polluted: megamorphic site that blocks type profiling
///  - generated: actual body generated by ObjectMethods::bootstrap
///  - specialized: generated body for non-extensible types
/// The result of generated compared to the other distinct/polluted shows
/// whether the generated code could perform type profiling.
/// Specialized is the result of distinct without trap, should be even faster.
@Fork(3)
@Warmup(iterations = 10, time = 1)
@Measurement(iterations = 5, time = 2)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.Throughput)
public class RecordMethodsBenchmark {

    record One(int a) {}

    @State(Scope.Thread)
    public static class BenchmarkState {
        Key k1 = new Key(new One(1), "a");
        Key k2 = new Key(new One(1), new String("a"));
        SpecializedKey sk1 = new SpecializedKey(new One(1), "a");
        SpecializedKey sk2 = new SpecializedKey(new One(1), new String("a"));
    }

    @Benchmark
    public int hashCodeDistinct(BenchmarkState state) {
        return state.k1.hashCodeDistinct();
    }

    @Benchmark
    public int hashCodePolluted(BenchmarkState state) {
        return state.k1.hashCodePolluted();
    }

    @Benchmark
    public int hashCodeGenerated(BenchmarkState state) {
        return state.k1.hashCode();
    }

    @Benchmark
    public int hashCodeSpecial(BenchmarkState state) {
        return state.sk1.hashCode();
    }

    @Benchmark
    public boolean equalsDistinct(BenchmarkState state) {
        return state.k1.equalsDistinct(state.k2);
    }

    @Benchmark
    public boolean equalsPolluted(BenchmarkState state) {
        return state.k1.equalsPolluted(state.k2);
    }

    @Benchmark
    public boolean equalsGenerated(BenchmarkState state) {
        return state.k1.equals(state.k2);
    }

    @Benchmark
    public boolean equalsSpecial(BenchmarkState state) {
        return state.sk1.equals(state.sk2);
    }

    /// A key object.
    ///
    /// Having both field as Object pollutes Object.equals for record object
    /// method MH tree.  We must verify the leaf Object.equals calls don't
    /// share the same profile in generated code.
    record Key(Object key1, Object key2) {
        /// A hashCode method which has distinct hashCode invocations
        /// in bytecode for each field for type profiling.
        public int hashCodeDistinct() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((key1 == null) ? 0 : key1.hashCode());
            result = prime * result + ((key2 == null) ? 0 : key2.hashCode());
            return result;
        }

        /// A hashCode method which uses a megamorphic polluted
        /// Object.hashCode virtual invocation in Objects.hashCode.
        public int hashCodePolluted() {
            final int prime = 31;
            int result = 1;
            result = prime * result + Objects.hashCode(key1);
            result = prime * result + Objects.hashCode(key2);
            return result;
        }

        /// An equals method which has distinct equals invocations
        /// in bytecode for each field for type profiling.
        public boolean equalsDistinct(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Key other = (Key) obj;
            if (key1 == null) {
                if (other.key1 != null)
                    return false;
            }
            else if (!key1.equals(other.key1))
                return false;
            if (key2 == null) {
                if (other.key2 != null)
                    return false;
            }
            else if (!key2.equals(other.key2))
                return false;
            return true;
        }

        /// An equals method which uses a megamorphic polluted
        /// Object.equals virtual invocation in Objects.equals.
        public boolean equalsPolluted(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Key other = (Key) obj;
            return Objects.equals(key1, other.key1) && Objects.equals(key2, other.key2);
        }
    }

    record SpecializedKey(One key1, String key2) {}
}
