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.construct.element;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.DynamicConstantDesc;
019import java.lang.constant.MethodHandleDesc;
020import java.lang.constant.MethodTypeDesc;
021
022import java.util.Objects;
023import java.util.Optional;
024
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027
028import java.util.stream.IntStream;
029
030import javax.lang.model.element.Name;
031
032import static java.lang.constant.ConstantDescs.BSM_INVOKE;
033import static java.lang.constant.ConstantDescs.CD_String;
034
035import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
036
037import static java.util.Objects.requireNonNull;
038
039/**
040 * A {@link Name} implementation based on {@link String}s.
041 *
042 * <p>This {@link Name} implementation differs from {@link StringName} in that there is no {@link
043 * org.microbean.construct.PrimordialDomain} involved, and therefore no notion of any kind of delegate.</p>
044 *
045 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
046 *
047 * @see Name
048 *
049 * @see StringName
050 */
051public final class SyntheticName implements Constable, Name {
052
053  private static final ConcurrentMap<String, SyntheticName> names = new ConcurrentHashMap<>();
054
055  private final String value;
056
057  /**
058   * Creates a new {@link SyntheticName}.
059   *
060   * @param value the actual name; must not be {@code null}
061   *
062   * @exception NullPointerException if {@code value} is {@code null}
063   */
064  private SyntheticName(final String value) {
065    super();
066    this.value = requireNonNull(value, "value");
067  }
068
069  @Override // Name (CharSequence)
070  public final char charAt(final int index) {
071    return this.value.charAt(index);
072  }
073
074  @Override // Name (CharSequence)
075  public final IntStream chars() {
076    return this.value.chars();
077  }
078
079  @Override // Name (CharSequence)
080  public final IntStream codePoints() {
081    return this.value.codePoints();
082  }
083
084  @Override // Name
085  public final boolean contentEquals(final CharSequence cs) {
086    return this == cs || cs != null && this.value.contentEquals(cs.toString());
087  }
088
089  @Override // Constable
090  public final Optional<DynamicConstantDesc<SyntheticName>> describeConstable() {
091    return
092      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
093                                         MethodHandleDesc.ofMethod(STATIC,
094                                                                   ClassDesc.of(this.getClass().getName()),
095                                                                   "of",
096                                                                   MethodTypeDesc.of(CD_String)),
097                                         this.value));
098  }
099
100  @Override // Object
101  public final boolean equals(final Object other) {
102    return this == other || switch (other) {
103    case null -> false;
104    case SyntheticName sn when this.getClass() == sn.getClass() -> Objects.equals(this.value, sn.value);
105    default -> false;
106    };
107  }
108
109  @Override // Object
110  public final int hashCode() {
111    return this.value.hashCode();
112  }
113
114  @Override // Name (CharSequence)
115  public final boolean isEmpty() {
116    return this.value.isEmpty();
117  }
118
119  @Override // Name (CharSequence)
120  public final int length() {
121    return this.value.length();
122  }
123
124  @Override // Name (CharSequence)
125  public final CharSequence subSequence(final int start, final int end) {
126    return this.value.subSequence(start, end);
127  }
128
129  @Override // Name (CharSequence)
130  public final String toString() {
131    return this.value;
132  }
133
134  /**
135   * Returns a (non-{@code null}) {@link SyntheticName} representing the supplied {@code name}.
136   *
137   * @param name a {@link String}; must not be {@code null}
138   *
139   * @return a {@link SyntheticName}; never {@code null}
140   *
141   * @exception NullPointerException if {@code name} is {@code null}
142   */
143  public static final SyntheticName of(final String name) {
144    return names.computeIfAbsent(name, SyntheticName::new);
145  }
146
147}