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}