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 024/** 025 * An immutable JSON string. 026 * 027 * <p>Construction rejects unpaired UTF-16 surrogates with 028 * {@link IllegalArgumentException} and retains well-formed text exactly, 029 * without normalization or replacement. It does not guarantee that the 030 * value can be sent on the MCP wire. Production JSON independently limits 031 * decoded strings and their escaped tokens to 1,048,576 UTF-16 code units, 032 * excluding the surrounding quotes. For example, each newline consumes one 033 * decoded unit but two token units. Object member names have the same limits. 034 * The complete response is also limited to 4 MiB of UTF-8 JSON.</p> 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 */ 038@ThreadSafe 039public final class McpJsonString implements McpJsonValue { 040 @NonNull 041 private final String value; 042 043 /** 044 * Creates an immutable JSON string from its value. 045 * 046 * @param value the non-null string value 047 * @return immutable JSON string 048 * @throws IllegalArgumentException if the value contains an unpaired surrogate 049 */ 050 @NonNull 051 public static McpJsonString fromValue(@NonNull String value) { 052 return new McpJsonString(value); 053 } 054 055 private McpJsonString(@NonNull String value) { 056 this.value = McpContentValueSupport.requireWellFormedString(value); 057 } 058 059 /** @return string value */ 060 @NonNull 061 public String getValue() { 062 return this.value; 063 } 064 065 /** @return whether this object contains the same string value */ 066 @Override 067 public boolean equals(@Nullable Object other) { 068 if (this == other) 069 return true; 070 if (!(other instanceof McpJsonString string)) 071 return false; 072 return this.value.equals(string.value); 073 } 074 075 /** @return value-based hash code */ 076 @Override 077 public int hashCode() { 078 return this.value.hashCode(); 079 } 080 081 /** @return redacted diagnostic rendering */ 082 @Override 083 @NonNull 084 public String toString() { 085 return "McpJsonString{value=<redacted>}"; 086 } 087}