001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–2026 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.construct.element;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020import java.lang.constant.MethodHandleDesc;
021
022import java.util.Objects;
023import java.util.Optional;
024
025import java.util.stream.IntStream;
026
027import javax.lang.model.element.Name;
028
029import org.microbean.construct.PrimordialDomain;
030
031import static java.lang.constant.ConstantDescs.BSM_INVOKE;
032
033import static java.util.Objects.requireNonNull;
034
035/**
036 * A {@link Name} implementation based on {@link String}s.
037 *
038 * <p>This {@link Name} implementation differs from {@link SyntheticName} in that it involves usage of the {@link
039 * PrimordialDomain#toString(Name)} method, which gives a {@link PrimordialDomain} implementation a chance to cache the
040 * underlying resulting {@link Name}.</p>
041 *
042 * @param value the actual name; must not be {@code null}
043 *
044 * @param domain a {@link PrimordialDomain}; must not be {@code null}
045 *
046 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
047 *
048 * @see Name
049 *
050 * @see SyntheticName
051 *
052 * @see PrimordialDomain#toString(CharSequence)
053 */
054public final record StringName(String value, PrimordialDomain domain) implements Constable, Name {
055
056  /**
057   * Creates a new {@link StringName}.
058   *
059   * @param value the actual name; must not be {@code null}
060   *
061   * @param domain a {@link PrimordialDomain}; must not be {@code null}
062   *
063   * @exception NullPointerException if either argument is {@code null}
064   */
065  public StringName(final CharSequence value, final PrimordialDomain domain) {
066    // We deliberately route even String-typed values through PrimordialDomain#toString(CharSequence) in case the PrimordialDomain wishes to
067    // cache the intermediate Name.
068    this(switch (value) {
069      case StringName sn -> sn.value;
070      default -> domain.toString(value);
071      }, domain);
072  }
073
074  /**
075   * Creates a new {@link StringName}.
076   *
077   * @param value the actual name; must not be {@code null}
078   *
079   * @param domain a {@link PrimordialDomain}; must not be {@code null}
080   *
081   * @exception NullPointerException if either argument is {@code null}
082   */
083  public StringName {
084    requireNonNull(value, "value");
085    requireNonNull(domain, "domain");
086  }
087
088  @Override // Name (CharSequence)
089  public final char charAt(final int index) {
090    return this.value.charAt(index);
091  }
092
093  @Override // Name (CharSequence)
094  public final IntStream chars() {
095    return this.value.chars();
096  }
097
098  @Override // Name (CharSequence)
099  public final IntStream codePoints() {
100    return this.value.codePoints();
101  }
102
103  @Override // Name
104  @SuppressWarnings("try")
105  public final boolean contentEquals(final CharSequence cs) {
106    return this == cs || switch (cs) {
107    case null -> false;
108    case String s -> this.value.contentEquals(s);
109    case StringName sn -> this.value.contentEquals(sn.value);
110    case Name n -> this.value.contentEquals(this.domain().toString(n));
111    default -> this.value.contentEquals(cs.toString());
112    };
113  }
114
115  @Override // Constable
116  public final Optional<? extends ConstantDesc> describeConstable() {
117    return (this.domain() instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
118      .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE,
119                                                MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()),
120                                                                               ClassDesc.of(CharSequence.class.getName()),
121                                                                               ClassDesc.of(PrimordialDomain.class.getName())),
122                                                this.value,
123                                                domainDesc));
124  }
125
126  @Override // Record
127  public final boolean equals(final Object other) {
128    return this == other || switch (other) {
129    case null -> false;
130    case StringName sn when this.getClass() == sn.getClass() -> Objects.equals(this.value, sn.value);
131    default -> false;
132    };
133  }
134
135  @Override // Record
136  public final int hashCode() {
137    return this.value.hashCode();
138  }
139
140  @Override // Name (CharSequence)
141  public final boolean isEmpty() {
142    return this.value.isEmpty();
143  }
144
145  @Override // Name (CharSequence)
146  public final int length() {
147    return this.value.length();
148  }
149
150  @Override // Name (CharSequence)
151  public final CharSequence subSequence(final int start, final int end) {
152    return this.value.subSequence(start, end);
153  }
154
155  @Override // Name (CharSequence)
156  public final String toString() {
157    return this.value;
158  }
159
160
161  /*
162   * Static methods.
163   */
164
165
166  /**
167   * Returns a {@link StringName} whose {@link #value()} method will return a {@link String} {@linkplain
168   * String#equals(Object) equal to} the {@linkplain PrimordialDomain#toString(CharSequence) <code>String</code>
169   * conversion of} the supplied {@link CharSequence}, and whose {@link #domain()} method will return a {@link
170   * PrimordialDomain} {@linkplain #equals(Object) equal to} the supplied {@link PrimordialDomain}.
171   *
172   * @param cs a {@link CharSequence}; must not be {@code null}
173   *
174   * @param domain a {@link PrimordialDomain}; must not be {@code null}
175   *
176   * @return a {@link StringName}; never {@code null}
177   *
178   * @exception NullPointerException if either argument is {@code null}
179   *
180   * @see PrimordialDomain#toString(CharSequence)
181   */
182  public static final StringName of(final CharSequence cs, final PrimordialDomain domain) {
183    return cs instanceof StringName sn ? sn : new StringName(domain.toString(cs), domain);
184  }
185
186}