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