001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.attributes;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.DynamicConstantDesc;
018import java.lang.constant.MethodHandleDesc;
019import java.lang.constant.MethodTypeDesc;
020
021import java.util.Objects;
022import java.util.Optional;
023
024import static java.lang.constant.ConstantDescs.BSM_INVOKE;
025import static java.lang.constant.ConstantDescs.CD_Class;
026import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
027
028/**
029 * A {@link Value} whose value is a {@code Class}.
030 *
031 * @param <T> the type of the class modeled by this {@link ClassValue}
032 *
033 * @param value the value
034 *
035 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
036 */
037public final record ClassValue<T>(Class<T> value) implements Value<ClassValue<T>> {
038
039  public static final ClassValue<Object> CLASS_JAVA_LANG_OBJECT = new ClassValue<>(Object.class);
040
041  public static final ClassValue<String> CLASS_JAVA_LANG_STRING = new ClassValue<>(String.class);
042
043  /**
044   * Creates a new {@link ClassValue}.
045   *
046   * @param value the value; must not be {@code null}
047   *
048   * @exception NullPointerException if {@code value} is {@code null}
049   */
050  public ClassValue {
051    Objects.requireNonNull(value, "value");
052  }
053
054  @Override // Comparable<ClassValue>
055  public final int compareTo(final ClassValue<T> other) {
056    if (other == null) {
057      return -1;
058    } else if (this.equals(other)) {
059      return 0;
060    }
061    return this.value().getName().compareTo(other.value().getName());
062  }
063
064  @Override // Constable
065  public final Optional<DynamicConstantDesc<ClassValue<T>>> describeConstable() {
066    final ClassDesc cd = ClassDesc.of(this.getClass().getName());
067    return
068      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
069                                         MethodHandleDesc.ofMethod(STATIC,
070                                                                   cd,
071                                                                   "of",
072                                                                   MethodTypeDesc.of(cd,
073                                                                                     CD_Class)),
074                                         this.value().describeConstable().orElseThrow()));
075  }
076
077  @Override // Record
078  public final boolean equals(final Object other) {
079    return
080      other == this ||
081      // Follow java.lang.annotation.Annotation requirements.
082      other != null && other.getClass() == this.getClass() && this.value().equals(((ClassValue)other).value());
083  }
084
085  @Override // Record
086  public final int hashCode() {
087    // Follow java.lang.annotation.Annotation requirements.
088    return this.value().hashCode();
089  }
090
091  @Override // Record
092  public final String toString() {
093    return this.value().getName(); // binary name
094  }
095
096  /**
097   * Returns a {@link ClassValue} suitable for the supplied arguments.
098   *
099   * @param <T> the type of the class
100   *
101   * @param c a {@code Class}; must not be {@code null}
102   *
103   * @return a non-{@code null} {@link ClassValue}
104   *
105   * @exception NullPointerException if {@code c} is {@code null}
106   */
107  @SuppressWarnings("unchecked")
108  public static final <T> ClassValue<T> of(final Class<T> c) {
109    return
110      c == Object.class ? (ClassValue<T>)CLASS_JAVA_LANG_OBJECT :
111      c == String.class ? (ClassValue<T>)CLASS_JAVA_LANG_STRING :
112      new ClassValue<>(c);
113  }
114
115}