001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * 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 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.ThreadSafe; 023 024import static java.util.Objects.requireNonNull; 025 026/** 027 * Immutable framework-owned MCP presentation field supplied to a localization 028 * provider. 029 * 030 * @author <a href="https://www.revetkn.com">Mark Allen</a> 031 */ 032@ThreadSafe 033public final class McpLocalizableText { 034 @NonNull 035 private final McpTextCoordinate coordinate; 036 @NonNull 037 private final String defaultText; 038 039 /** 040 * Creates localizable text from its coordinate and canonical default text. 041 * 042 * @param coordinate stable structured coordinate 043 * @param defaultText canonical configured default text and safe fallback 044 * @return immutable localizable text 045 * @throws NullPointerException if either argument is null 046 * @throws IllegalArgumentException if {@code defaultText} is blank or contains malformed UTF-16 047 */ 048 @NonNull 049 public static McpLocalizableText fromCoordinateAndDefaultText( 050 @NonNull McpTextCoordinate coordinate, 051 @NonNull String defaultText) { 052 return new McpLocalizableText(coordinate, defaultText); 053 } 054 055 McpLocalizableText(@NonNull McpTextCoordinate coordinate, 056 @NonNull String defaultText) { 057 this.coordinate = requireNonNull(coordinate); 058 this.defaultText = McpContentValueSupport.requireWellFormedString(requireNonNull(defaultText)); 059 if (defaultText.isBlank()) 060 throw new IllegalArgumentException( 061 "Localizable MCP default text must not be blank."); 062 } 063 064 /** @return stable structured coordinate */ 065 @NonNull 066 public McpTextCoordinate getCoordinate() { 067 return this.coordinate; 068 } 069 070 /** @return canonical configured default text and safe fallback */ 071 @NonNull 072 public String getDefaultText() { 073 return this.defaultText; 074 } 075 076 /** @return whether coordinate and canonical default text are equal */ 077 @Override 078 public boolean equals(@Nullable Object other) { 079 if (this == other) 080 return true; 081 if (!(other instanceof McpLocalizableText text)) 082 return false; 083 return this.coordinate.equals(text.coordinate) 084 && this.defaultText.equals(text.defaultText); 085 } 086 087 /** @return value-based hash code */ 088 @Override 089 public int hashCode() { 090 return 31 * this.coordinate.hashCode() + this.defaultText.hashCode(); 091 } 092 093 /** @return rendering that never exposes default text */ 094 @Override 095 @NonNull 096 public String toString() { 097 return "McpLocalizableText{coordinate=<redacted>, " 098 + "defaultText=<redacted>}"; 099 } 100}