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}