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