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}