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.ThreadSafe; 023import java.util.Collections; 024import java.util.LinkedHashSet; 025import java.util.Locale; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.Set; 029 030import static java.util.Objects.requireNonNull; 031 032/** 033 * Describes a non-fatal validation problem detected while loading a localized strings file. 034 * <p> 035 * Instances are supplied to a {@link LocalizedStringWarningHandler}. Lokalized constructs these objects; 036 * application code should normally only inspect them. A warning never aborts loading on its own - the configured 037 * handler decides what to do (log it, ignore it, collect it, or throw to fail the load). 038 * 039 * @author <a href="https://revetkn.com">Mark Allen</a> 040 * @since 3.0.0 041 */ 042@ThreadSafe 043public final class LocalizedStringWarning { 044 @NonNull 045 private final Type type; 046 @NonNull 047 private final String source; 048 @Nullable 049 private final Locale locale; 050 @Nullable 051 private final String key; 052 @Nullable 053 private final String placeholder; 054 @NonNull 055 private final Set<@NonNull String> missingLanguageForms; 056 @NonNull 057 private final String message; 058 059 /** 060 * The kind of problem a {@link LocalizedStringWarning} represents. 061 * 062 * @since 3.0.0 063 */ 064 public enum Type { 065 /** 066 * A cardinality-driven placeholder omits one or more cardinal forms that its locale requires per CLDR. 067 */ 068 INCOMPLETE_CARDINALITY_TRANSLATIONS, 069 /** 070 * An ordinality-driven placeholder omits one or more ordinal forms that its locale requires per CLDR. 071 */ 072 INCOMPLETE_ORDINALITY_TRANSLATIONS, 073 /** 074 * A JSON resource in a classpath localized strings package is not named with a valid IETF BCP 47 locale tag. 075 */ 076 INVALID_CLASSPATH_LOCALE_FILENAME 077 } 078 079 /** 080 * Constructs a validation warning. 081 * 082 * @param type the kind of problem, not null 083 * @param source the file path or URL being loaded, not null 084 * @param locale the locale the file is being loaded for, not null 085 * @param key the translation key that triggered the warning, not null 086 * @param placeholder the placeholder within the key that triggered the warning, not null 087 * @param missingLanguageForms the missing language-form names in file format (e.g. {@code CARDINALITY_MANY}), not null 088 * @param message a human-readable description of the warning, not null 089 */ 090 LocalizedStringWarning(@NonNull Type type, 091 @NonNull String source, 092 @NonNull Locale locale, 093 @NonNull String key, 094 @NonNull String placeholder, 095 @NonNull Set<@NonNull String> missingLanguageForms, 096 @NonNull String message) { 097 requireNonNull(type); 098 requireNonNull(source); 099 requireNonNull(locale); 100 requireNonNull(key); 101 requireNonNull(placeholder); 102 requireNonNull(missingLanguageForms); 103 requireNonNull(message); 104 105 this.type = type; 106 this.source = source; 107 this.locale = locale; 108 this.key = key; 109 this.placeholder = placeholder; 110 this.missingLanguageForms = Collections.unmodifiableSet(new LinkedHashSet<>(missingLanguageForms)); 111 this.message = message; 112 } 113 114 LocalizedStringWarning(@NonNull Type type, 115 @NonNull String source, 116 @NonNull String message) { 117 requireNonNull(type); 118 requireNonNull(source); 119 requireNonNull(message); 120 121 this.type = type; 122 this.source = source; 123 this.locale = null; 124 this.key = null; 125 this.placeholder = null; 126 this.missingLanguageForms = Collections.emptySet(); 127 this.message = message; 128 } 129 130 /** 131 * Gets the kind of problem this warning represents. 132 * 133 * @return the warning type, not null 134 */ 135 @NonNull 136 public Type getType() { 137 return this.type; 138 } 139 140 /** 141 * Gets the file path or URL that was being loaded. 142 * 143 * @return the source, not null 144 */ 145 @NonNull 146 public String getSource() { 147 return this.source; 148 } 149 150 /** 151 * Gets the locale the file was being loaded for, when the warning applies to a parsed localized strings file. 152 * 153 * @return the locale, if applicable, not null 154 */ 155 @NonNull 156 public Optional<@NonNull Locale> getLocale() { 157 return Optional.ofNullable(this.locale); 158 } 159 160 /** 161 * Gets the translation key that triggered the warning, when applicable. 162 * 163 * @return the translation key, if applicable, not null 164 */ 165 @NonNull 166 public Optional<@NonNull String> getKey() { 167 return Optional.ofNullable(this.key); 168 } 169 170 /** 171 * Gets the placeholder within the key that triggered the warning, when applicable. 172 * 173 * @return the placeholder, if applicable, not null 174 */ 175 @NonNull 176 public Optional<@NonNull String> getPlaceholder() { 177 return Optional.ofNullable(this.placeholder); 178 } 179 180 /** 181 * Gets the missing language-form names, in localized strings file format (e.g. {@code CARDINALITY_MANY}). 182 * 183 * @return the missing language-form names, not null 184 */ 185 @NonNull 186 public Set<@NonNull String> getMissingLanguageForms() { 187 return this.missingLanguageForms; 188 } 189 190 /** 191 * Gets a human-readable description of the warning. 192 * 193 * @return the message, not null 194 */ 195 @NonNull 196 public String getMessage() { 197 return this.message; 198 } 199 200 @Override 201 @NonNull 202 public String toString() { 203 return this.message; 204 } 205 206 @Override 207 public boolean equals(@Nullable Object object) { 208 if (this == object) 209 return true; 210 211 if (!(object instanceof LocalizedStringWarning)) 212 return false; 213 214 LocalizedStringWarning that = (LocalizedStringWarning) object; 215 216 return this.type == that.type 217 && Objects.equals(this.source, that.source) 218 && Objects.equals(this.locale, that.locale) 219 && Objects.equals(this.key, that.key) 220 && Objects.equals(this.placeholder, that.placeholder) 221 && Objects.equals(this.missingLanguageForms, that.missingLanguageForms) 222 && Objects.equals(this.message, that.message); 223 } 224 225 @Override 226 public int hashCode() { 227 return Objects.hash(this.type, this.source, this.locale, this.key, this.placeholder, this.missingLanguageForms, 228 this.message); 229 } 230}