001/* 002 * Copyright 2017-2022 Product Mog LLC, 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.lokalized; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.Immutable; 023import javax.annotation.concurrent.NotThreadSafe; 024import java.util.Objects; 025 026import static com.lokalized.Diagnostics.format; 027 028/** 029 * Immutable safety limits for translation construction and evaluation. 030 * <p> 031 * The defaults are deliberately generous enough for normal localized strings while bounding work that is proportional to 032 * untrusted localized strings data or runtime values. Builders may customize a limit through the library's hard ceiling. 033 * Localized strings loaders validate expressions against hard ceilings because an application's runtime policy is not yet 034 * available; {@link Strings} construction then enforces its configured limits. A single instance is safe to share 035 * between {@link Strings} instances and threads. 036 * <p> 037 * Caller-supplied {@link CharSequence} values are length-checked before Lokalized scans, copies, bidi-isolates, or 038 * materializes them for phonetic resolution. The interpolated-output limit also bounds one phonetic input. For other 039 * placeholder object types, Lokalized must invoke application-defined {@link Object#toString()} code before it can 040 * measure the result; these limits bound Lokalized's subsequent work but cannot bound work performed inside that 041 * application method. Use a pre-bounded {@code String} or {@code CharSequence} when runtime values originate from 042 * untrusted input. 043 * <p> 044 * Expression source, token, and nesting limits apply equally to whole-message alternative predicates and 045 * {@link LocalizedString.ExpressionAlternative expression-fragment predicates}. Generated-placeholder depth and 046 * cumulative expansion limits are shared by {@link LocalizedString.LanguageFormTranslation language-form fragments} 047 * and {@link LocalizedString.ExpressionTranslation expression-selected fragments}, including dependencies that cross 048 * between the two kinds. 049 * 050 * @author <a href="https://revetkn.com">Mark Allen</a> 051 * @since 3.0.0 052 */ 053@Immutable 054public final class TranslationRuntimeLimits { 055 /** Default limit for decimal precision: 1,024. */ 056 @NonNull public static final Integer DEFAULT_MAXIMUM_NUMBER_PRECISION = 1_024; 057 /** Default limit for the absolute value of a decimal scale: 1,024. */ 058 @NonNull public static final Integer DEFAULT_MAXIMUM_ABSOLUTE_NUMBER_SCALE = 1_024; 059 /** Default limit for explicitly visible decimal places: 1,024. */ 060 @NonNull public static final Integer DEFAULT_MAXIMUM_VISIBLE_DECIMAL_PLACES = 1_024; 061 /** Default limit for compact-decimal exponents: 64. */ 062 @NonNull public static final Integer DEFAULT_MAXIMUM_COMPACT_EXPONENT = 64; 063 /** Default limit for characters in one whole-message or generated-fragment expression: 2,048. */ 064 @NonNull public static final Integer DEFAULT_MAXIMUM_EXPRESSION_CHARACTERS = 2_048; 065 /** Default limit for tokens in one whole-message or generated-fragment expression: 256. */ 066 @NonNull public static final Integer DEFAULT_MAXIMUM_EXPRESSION_TOKENS = 256; 067 /** Default limit for nested groups in one whole-message or generated-fragment expression: 32. */ 068 @NonNull public static final Integer DEFAULT_MAXIMUM_EXPRESSION_NESTING_DEPTH = 32; 069 /** Default limit for nested generated placeholders across both definition kinds: 32. */ 070 @NonNull public static final Integer DEFAULT_MAXIMUM_GENERATED_PLACEHOLDER_DEPTH = 32; 071 /** Default limit for one interpolated message, generated fragment, or phonetic input: 262,144 UTF-16 code units. */ 072 @NonNull public static final Integer DEFAULT_MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS = 256 * 1_024; 073 /** 074 * Default cumulative expansion limit across both generated-placeholder kinds: 1,048,576 UTF-16 code units per 075 * locale fallback attempt. 076 */ 077 @NonNull public static final Integer DEFAULT_MAXIMUM_GENERATED_EXPANSION_CHARACTERS = 1_024 * 1_024; 078 079 /** Hard ceiling for decimal precision: 4,096. */ 080 @NonNull public static final Integer MAXIMUM_NUMBER_PRECISION = 4_096; 081 /** Hard ceiling for the absolute value of a decimal scale: 4,096. */ 082 @NonNull public static final Integer MAXIMUM_ABSOLUTE_NUMBER_SCALE = 4_096; 083 /** Hard ceiling for explicitly visible decimal places: 4,096. */ 084 @NonNull public static final Integer MAXIMUM_VISIBLE_DECIMAL_PLACES = 4_096; 085 /** Hard ceiling for compact-decimal exponents: 4,096. */ 086 @NonNull public static final Integer MAXIMUM_COMPACT_EXPONENT = 4_096; 087 /** Hard ceiling for characters in one whole-message or generated-fragment expression: 4,096. */ 088 @NonNull public static final Integer MAXIMUM_EXPRESSION_CHARACTERS = 4_096; 089 /** Hard ceiling for tokens in one whole-message or generated-fragment expression: 512. */ 090 @NonNull public static final Integer MAXIMUM_EXPRESSION_TOKENS = 512; 091 /** Hard ceiling for nested groups in one whole-message or generated-fragment expression: 64. */ 092 @NonNull public static final Integer MAXIMUM_EXPRESSION_NESTING_DEPTH = 64; 093 /** Hard ceiling for nested generated placeholders across both definition kinds: 64. */ 094 @NonNull public static final Integer MAXIMUM_GENERATED_PLACEHOLDER_DEPTH = 64; 095 /** Hard ceiling for one interpolated message, generated fragment, or phonetic input: 1,048,576 UTF-16 code units. */ 096 @NonNull public static final Integer MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS = 1_024 * 1_024; 097 /** 098 * Hard ceiling for cumulative expansion across both generated-placeholder kinds: 8,388,608 UTF-16 code units per 099 * locale fallback attempt. 100 */ 101 @NonNull public static final Integer MAXIMUM_GENERATED_EXPANSION_CHARACTERS = 8 * 1_024 * 1_024; 102 103 @NonNull 104 private static final TranslationRuntimeLimits DEFAULTS = new Builder().build(); 105 @NonNull 106 private static final TranslationRuntimeLimits HARD_CEILINGS = new Builder() 107 .maximumNumberPrecision(MAXIMUM_NUMBER_PRECISION) 108 .maximumAbsoluteNumberScale(MAXIMUM_ABSOLUTE_NUMBER_SCALE) 109 .maximumVisibleDecimalPlaces(MAXIMUM_VISIBLE_DECIMAL_PLACES) 110 .maximumCompactExponent(MAXIMUM_COMPACT_EXPONENT) 111 .maximumExpressionCharacters(MAXIMUM_EXPRESSION_CHARACTERS) 112 .maximumExpressionTokens(MAXIMUM_EXPRESSION_TOKENS) 113 .maximumExpressionNestingDepth(MAXIMUM_EXPRESSION_NESTING_DEPTH) 114 .maximumGeneratedPlaceholderDepth(MAXIMUM_GENERATED_PLACEHOLDER_DEPTH) 115 .maximumInterpolatedOutputCharacters(MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS) 116 .maximumGeneratedExpansionCharacters(MAXIMUM_GENERATED_EXPANSION_CHARACTERS) 117 .build(); 118 119 @NonNull private final Integer maximumNumberPrecision; 120 @NonNull private final Integer maximumAbsoluteNumberScale; 121 @NonNull private final Integer maximumVisibleDecimalPlaces; 122 @NonNull private final Integer maximumCompactExponent; 123 @NonNull private final Integer maximumExpressionCharacters; 124 @NonNull private final Integer maximumExpressionTokens; 125 @NonNull private final Integer maximumExpressionNestingDepth; 126 @NonNull private final Integer maximumGeneratedPlaceholderDepth; 127 @NonNull private final Integer maximumInterpolatedOutputCharacters; 128 @NonNull private final Integer maximumGeneratedExpansionCharacters; 129 130 private TranslationRuntimeLimits(@NonNull Builder builder) { 131 this.maximumNumberPrecision = builder.maximumNumberPrecision; 132 this.maximumAbsoluteNumberScale = builder.maximumAbsoluteNumberScale; 133 this.maximumVisibleDecimalPlaces = builder.maximumVisibleDecimalPlaces; 134 this.maximumCompactExponent = builder.maximumCompactExponent; 135 this.maximumExpressionCharacters = builder.maximumExpressionCharacters; 136 this.maximumExpressionTokens = builder.maximumExpressionTokens; 137 this.maximumExpressionNestingDepth = builder.maximumExpressionNestingDepth; 138 this.maximumGeneratedPlaceholderDepth = builder.maximumGeneratedPlaceholderDepth; 139 this.maximumInterpolatedOutputCharacters = builder.maximumInterpolatedOutputCharacters; 140 this.maximumGeneratedExpansionCharacters = builder.maximumGeneratedExpansionCharacters; 141 } 142 143 /** 144 * Gets the library-default limits. 145 * 146 * @return the library-default limits, not null 147 */ 148 @NonNull public static TranslationRuntimeLimits defaults() { return DEFAULTS; } 149 150 /** 151 * Gets the library hard ceilings. 152 * 153 * @return the library hard ceilings, not null 154 */ 155 @NonNull static TranslationRuntimeLimits hardCeilings() { return HARD_CEILINGS; } 156 157 /** 158 * Creates a builder initialized to the library defaults. 159 * 160 * @return a builder initialized to the library defaults, not null 161 */ 162 @NonNull public static Builder builder() { return new Builder(); } 163 164 /** 165 * Creates a builder initialized from this instance. 166 * 167 * @return a builder initialized from this instance, not null 168 */ 169 @NonNull public Builder toBuilder() { return new Builder(this); } 170 171 /** 172 * Gets the maximum decimal precision. 173 * 174 * @return maximum decimal precision, not null 175 */ 176 @NonNull public Integer getMaximumNumberPrecision() { return maximumNumberPrecision; } 177 /** 178 * Gets the maximum absolute decimal scale. 179 * 180 * @return maximum absolute decimal scale, not null 181 */ 182 @NonNull public Integer getMaximumAbsoluteNumberScale() { return maximumAbsoluteNumberScale; } 183 /** 184 * Gets the maximum number of explicitly visible decimal places. 185 * 186 * @return maximum explicitly visible decimal places, not null 187 */ 188 @NonNull public Integer getMaximumVisibleDecimalPlaces() { return maximumVisibleDecimalPlaces; } 189 /** 190 * Gets the maximum compact-decimal exponent. 191 * 192 * @return maximum compact-decimal exponent, not null 193 */ 194 @NonNull public Integer getMaximumCompactExponent() { return maximumCompactExponent; } 195 /** 196 * Gets the maximum characters in one whole-message or generated-fragment expression. 197 * 198 * @return maximum characters in one whole-message or generated-fragment expression, not null 199 */ 200 @NonNull public Integer getMaximumExpressionCharacters() { return maximumExpressionCharacters; } 201 /** 202 * Gets the maximum tokens in one whole-message or generated-fragment expression. 203 * 204 * @return maximum tokens in one whole-message or generated-fragment expression, not null 205 */ 206 @NonNull public Integer getMaximumExpressionTokens() { return maximumExpressionTokens; } 207 /** 208 * Gets the maximum nested groups in one whole-message or generated-fragment expression. 209 * 210 * @return maximum nested groups in one whole-message or generated-fragment expression, not null 211 */ 212 @NonNull public Integer getMaximumExpressionNestingDepth() { return maximumExpressionNestingDepth; } 213 /** 214 * Gets the maximum generated-placeholder nesting depth across both definition kinds. 215 * 216 * @return maximum generated-placeholder nesting depth across both definition kinds, not null 217 */ 218 @NonNull public Integer getMaximumGeneratedPlaceholderDepth() { return maximumGeneratedPlaceholderDepth; } 219 /** 220 * Gets the maximum UTF-16 code units in one interpolated message, generated fragment, or caller-supplied phonetic 221 * input. 222 * 223 * @return maximum UTF-16 code units in one interpolated message, generated fragment, or phonetic input, not null 224 */ 225 @NonNull public Integer getMaximumInterpolatedOutputCharacters() { return maximumInterpolatedOutputCharacters; } 226 /** 227 * Gets the maximum cumulative expansion across both generated-placeholder kinds per locale fallback attempt. 228 * 229 * @return maximum cumulative expansion across both generated-placeholder kinds in UTF-16 code units per 230 * locale fallback attempt, not null 231 */ 232 @NonNull public Integer getMaximumGeneratedExpansionCharacters() { return maximumGeneratedExpansionCharacters; } 233 234 @Override 235 public boolean equals(@Nullable Object object) { 236 if (this == object) 237 return true; 238 if (!(object instanceof TranslationRuntimeLimits)) 239 return false; 240 TranslationRuntimeLimits that = (TranslationRuntimeLimits) object; 241 return Objects.equals(maximumNumberPrecision, that.maximumNumberPrecision) 242 && Objects.equals(maximumAbsoluteNumberScale, that.maximumAbsoluteNumberScale) 243 && Objects.equals(maximumVisibleDecimalPlaces, that.maximumVisibleDecimalPlaces) 244 && Objects.equals(maximumCompactExponent, that.maximumCompactExponent) 245 && Objects.equals(maximumExpressionCharacters, that.maximumExpressionCharacters) 246 && Objects.equals(maximumExpressionTokens, that.maximumExpressionTokens) 247 && Objects.equals(maximumExpressionNestingDepth, that.maximumExpressionNestingDepth) 248 && Objects.equals(maximumGeneratedPlaceholderDepth, that.maximumGeneratedPlaceholderDepth) 249 && Objects.equals(maximumInterpolatedOutputCharacters, that.maximumInterpolatedOutputCharacters) 250 && Objects.equals(maximumGeneratedExpansionCharacters, that.maximumGeneratedExpansionCharacters); 251 } 252 253 @Override 254 public int hashCode() { 255 return Objects.hash(maximumNumberPrecision, maximumAbsoluteNumberScale, maximumVisibleDecimalPlaces, 256 maximumCompactExponent, maximumExpressionCharacters, maximumExpressionTokens, 257 maximumExpressionNestingDepth, maximumGeneratedPlaceholderDepth, 258 maximumInterpolatedOutputCharacters, maximumGeneratedExpansionCharacters); 259 } 260 261 @Override 262 @NonNull 263 public String toString() { 264 return format("%s{maximumNumberPrecision=%d, maximumAbsoluteNumberScale=%d, " + 265 "maximumVisibleDecimalPlaces=%d, maximumCompactExponent=%d, maximumExpressionCharacters=%d, " + 266 "maximumExpressionTokens=%d, maximumExpressionNestingDepth=%d, " + 267 "maximumGeneratedPlaceholderDepth=%d, maximumInterpolatedOutputCharacters=%d, " + 268 "maximumGeneratedExpansionCharacters=%d}", getClass().getSimpleName(), maximumNumberPrecision, 269 maximumAbsoluteNumberScale, maximumVisibleDecimalPlaces, maximumCompactExponent, 270 maximumExpressionCharacters, maximumExpressionTokens, maximumExpressionNestingDepth, 271 maximumGeneratedPlaceholderDepth, maximumInterpolatedOutputCharacters, 272 maximumGeneratedExpansionCharacters); 273 } 274 275 /** 276 * Builder for {@link TranslationRuntimeLimits}. 277 * 278 * @since 3.0.0 279 */ 280 @NotThreadSafe 281 public static final class Builder { 282 @NonNull private Integer maximumNumberPrecision = DEFAULT_MAXIMUM_NUMBER_PRECISION; 283 @NonNull private Integer maximumAbsoluteNumberScale = DEFAULT_MAXIMUM_ABSOLUTE_NUMBER_SCALE; 284 @NonNull private Integer maximumVisibleDecimalPlaces = DEFAULT_MAXIMUM_VISIBLE_DECIMAL_PLACES; 285 @NonNull private Integer maximumCompactExponent = DEFAULT_MAXIMUM_COMPACT_EXPONENT; 286 @NonNull private Integer maximumExpressionCharacters = DEFAULT_MAXIMUM_EXPRESSION_CHARACTERS; 287 @NonNull private Integer maximumExpressionTokens = DEFAULT_MAXIMUM_EXPRESSION_TOKENS; 288 @NonNull private Integer maximumExpressionNestingDepth = DEFAULT_MAXIMUM_EXPRESSION_NESTING_DEPTH; 289 @NonNull private Integer maximumGeneratedPlaceholderDepth = DEFAULT_MAXIMUM_GENERATED_PLACEHOLDER_DEPTH; 290 @NonNull private Integer maximumInterpolatedOutputCharacters = DEFAULT_MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS; 291 @NonNull private Integer maximumGeneratedExpansionCharacters = DEFAULT_MAXIMUM_GENERATED_EXPANSION_CHARACTERS; 292 293 private Builder() {} 294 295 private Builder(@NonNull TranslationRuntimeLimits limits) { 296 this.maximumNumberPrecision = limits.maximumNumberPrecision; 297 this.maximumAbsoluteNumberScale = limits.maximumAbsoluteNumberScale; 298 this.maximumVisibleDecimalPlaces = limits.maximumVisibleDecimalPlaces; 299 this.maximumCompactExponent = limits.maximumCompactExponent; 300 this.maximumExpressionCharacters = limits.maximumExpressionCharacters; 301 this.maximumExpressionTokens = limits.maximumExpressionTokens; 302 this.maximumExpressionNestingDepth = limits.maximumExpressionNestingDepth; 303 this.maximumGeneratedPlaceholderDepth = limits.maximumGeneratedPlaceholderDepth; 304 this.maximumInterpolatedOutputCharacters = limits.maximumInterpolatedOutputCharacters; 305 this.maximumGeneratedExpansionCharacters = limits.maximumGeneratedExpansionCharacters; 306 } 307 308 /** 309 * Sets the decimal-precision limit. 310 * 311 * @param value value from 1 through {@link #MAXIMUM_NUMBER_PRECISION}, or null to restore the default 312 * @return this builder, not null 313 */ 314 @NonNull public Builder maximumNumberPrecision(@Nullable Integer value) { 315 this.maximumNumberPrecision = value == null ? DEFAULT_MAXIMUM_NUMBER_PRECISION : value; 316 return this; 317 } 318 /** 319 * Sets the absolute decimal-scale limit. 320 * 321 * @param value value from 0 through {@link #MAXIMUM_ABSOLUTE_NUMBER_SCALE}, or null to restore the default 322 * @return this builder, not null 323 */ 324 @NonNull public Builder maximumAbsoluteNumberScale(@Nullable Integer value) { 325 this.maximumAbsoluteNumberScale = value == null ? DEFAULT_MAXIMUM_ABSOLUTE_NUMBER_SCALE : value; 326 return this; 327 } 328 /** 329 * Sets the explicitly visible-decimal-place limit. 330 * 331 * @param value value from 0 through {@link #MAXIMUM_VISIBLE_DECIMAL_PLACES}, or null to restore the default 332 * @return this builder, not null 333 */ 334 @NonNull public Builder maximumVisibleDecimalPlaces(@Nullable Integer value) { 335 this.maximumVisibleDecimalPlaces = value == null ? DEFAULT_MAXIMUM_VISIBLE_DECIMAL_PLACES : value; 336 return this; 337 } 338 /** 339 * Sets the compact-decimal-exponent limit. 340 * 341 * @param value value from 0 through {@link #MAXIMUM_COMPACT_EXPONENT}, or null to restore the default 342 * @return this builder, not null 343 */ 344 @NonNull public Builder maximumCompactExponent(@Nullable Integer value) { 345 this.maximumCompactExponent = value == null ? DEFAULT_MAXIMUM_COMPACT_EXPONENT : value; 346 return this; 347 } 348 /** 349 * Sets the source-length limit for each whole-message or generated-fragment expression. 350 * 351 * @param value value from 1 through {@link #MAXIMUM_EXPRESSION_CHARACTERS}, or null to restore the default 352 * @return this builder, not null 353 */ 354 @NonNull public Builder maximumExpressionCharacters(@Nullable Integer value) { 355 this.maximumExpressionCharacters = value == null ? DEFAULT_MAXIMUM_EXPRESSION_CHARACTERS : value; 356 return this; 357 } 358 /** 359 * Sets the token-count limit for each whole-message or generated-fragment expression. 360 * 361 * @param value value from 1 through {@link #MAXIMUM_EXPRESSION_TOKENS}, or null to restore the default 362 * @return this builder, not null 363 */ 364 @NonNull public Builder maximumExpressionTokens(@Nullable Integer value) { 365 this.maximumExpressionTokens = value == null ? DEFAULT_MAXIMUM_EXPRESSION_TOKENS : value; 366 return this; 367 } 368 /** 369 * Sets the nested-group limit for each whole-message or generated-fragment expression. 370 * 371 * @param value value from 0 through {@link #MAXIMUM_EXPRESSION_NESTING_DEPTH}, or null to restore the default 372 * @return this builder, not null 373 */ 374 @NonNull public Builder maximumExpressionNestingDepth(@Nullable Integer value) { 375 this.maximumExpressionNestingDepth = value == null ? DEFAULT_MAXIMUM_EXPRESSION_NESTING_DEPTH : value; 376 return this; 377 } 378 /** 379 * Sets the generated-placeholder nesting limit shared by language-form and expression-selected fragments. 380 * 381 * @param value value from 0 through {@link #MAXIMUM_GENERATED_PLACEHOLDER_DEPTH}, or null to restore the default 382 * @return this builder, not null 383 */ 384 @NonNull public Builder maximumGeneratedPlaceholderDepth(@Nullable Integer value) { 385 this.maximumGeneratedPlaceholderDepth = value == null ? DEFAULT_MAXIMUM_GENERATED_PLACEHOLDER_DEPTH : value; 386 return this; 387 } 388 /** 389 * Sets the maximum UTF-16 code-unit count for one interpolated message, generated fragment, or caller-supplied 390 * phonetic input. 391 * 392 * @param value value from 1 through {@link #MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS}, or null to restore the default 393 * @return this builder, not null 394 */ 395 @NonNull public Builder maximumInterpolatedOutputCharacters(@Nullable Integer value) { 396 this.maximumInterpolatedOutputCharacters = value == null ? DEFAULT_MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS : value; 397 return this; 398 } 399 /** 400 * Sets the cumulative UTF-16 code-unit budget shared by language-form and expression-selected generated fragments 401 * in one locale fallback attempt. Locale fallback starts a fresh budget for each candidate. 402 * 403 * @param value value from 0 through {@link #MAXIMUM_GENERATED_EXPANSION_CHARACTERS}, or null to restore the default 404 * @return this builder, not null 405 */ 406 @NonNull public Builder maximumGeneratedExpansionCharacters(@Nullable Integer value) { 407 this.maximumGeneratedExpansionCharacters = value == null ? DEFAULT_MAXIMUM_GENERATED_EXPANSION_CHARACTERS : value; 408 return this; 409 } 410 411 /** 412 * Builds immutable runtime limits. 413 * 414 * @return immutable runtime limits, not null 415 * @throws IllegalArgumentException if a configured value is outside its documented range 416 */ 417 @NonNull 418 public TranslationRuntimeLimits build() { 419 validatePositive("maximumNumberPrecision", maximumNumberPrecision, MAXIMUM_NUMBER_PRECISION); 420 validateNonNegative("maximumAbsoluteNumberScale", maximumAbsoluteNumberScale, MAXIMUM_ABSOLUTE_NUMBER_SCALE); 421 validateNonNegative("maximumVisibleDecimalPlaces", maximumVisibleDecimalPlaces, MAXIMUM_VISIBLE_DECIMAL_PLACES); 422 validateNonNegative("maximumCompactExponent", maximumCompactExponent, MAXIMUM_COMPACT_EXPONENT); 423 validatePositive("maximumExpressionCharacters", maximumExpressionCharacters, MAXIMUM_EXPRESSION_CHARACTERS); 424 validatePositive("maximumExpressionTokens", maximumExpressionTokens, MAXIMUM_EXPRESSION_TOKENS); 425 validateNonNegative("maximumExpressionNestingDepth", maximumExpressionNestingDepth, MAXIMUM_EXPRESSION_NESTING_DEPTH); 426 validateNonNegative("maximumGeneratedPlaceholderDepth", maximumGeneratedPlaceholderDepth, MAXIMUM_GENERATED_PLACEHOLDER_DEPTH); 427 validatePositive("maximumInterpolatedOutputCharacters", maximumInterpolatedOutputCharacters, MAXIMUM_INTERPOLATED_OUTPUT_CHARACTERS); 428 validateNonNegative("maximumGeneratedExpansionCharacters", maximumGeneratedExpansionCharacters, MAXIMUM_GENERATED_EXPANSION_CHARACTERS); 429 return new TranslationRuntimeLimits(this); 430 } 431 432 private static void validatePositive(@NonNull String name, @NonNull Integer value, @NonNull Integer ceiling) { 433 if (value <= 0) 434 throw new IllegalArgumentException(format("%s must be positive, but was %d", name, value)); 435 validateCeiling(name, value, ceiling); 436 } 437 438 private static void validateNonNegative(@NonNull String name, @NonNull Integer value, @NonNull Integer ceiling) { 439 if (value < 0) 440 throw new IllegalArgumentException(format("%s must be non-negative, but was %d", name, value)); 441 validateCeiling(name, value, ceiling); 442 } 443 444 private static void validateCeiling(@NonNull String name, @NonNull Integer value, @NonNull Integer ceiling) { 445 if (value > ceiling) 446 throw new IllegalArgumentException(format("%s %d exceeds the hard ceiling of %d", name, value, ceiling)); 447 } 448 } 449}