From 85e44df3c9f479c8c0a2b45fe41cc4aac8f488e0 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Fri, 20 Feb 2026 09:24:27 +0200 Subject: [PATCH 1/7] mark all APIs nullable/non-nullable JSpecify is de-facto standard annotations for marking nullability of variables, parameters, return values and fields. Supported by all IDEs. 1. By default, we mark everything as non-nullable (see @NullMarked in package-info.java) 2. When needed, we mark the nullable parameters/fields as @Nullable. --- pom.xml | 16 +++++++ .../datafaker/annotations/FakeResolver.java | 45 ++++++++++--------- .../datafaker/annotations/package-info.java | 7 +++ .../datafaker/idnumbers/EstonianIdNumber.java | 2 +- .../idnumbers/HungarianIdNumber.java | 6 +-- .../idnumbers/IdNumberGenerator.java | 2 +- .../datafaker/idnumbers/PolishIdNumber.java | 3 +- .../net/datafaker/idnumbers/package-info.java | 6 +++ .../pt/br/IdNumberGeneratorPtBrUtil.java | 2 +- .../idnumbers/pt/br/package-info.java | 6 +++ .../datafaker/internal/helper/JavaNames.java | 5 ++- .../internal/helper/LazyEvaluated.java | 7 ++- .../internal/helper/SingletonLocale.java | 27 +++++------ .../datafaker/internal/helper/WordUtils.java | 13 +++++- .../internal/helper/package-info.java | 6 +++ src/main/java/net/datafaker/package-info.java | 6 +++ .../net/datafaker/providers/base/Animal.java | 4 +- .../net/datafaker/providers/base/Barcode.java | 4 +- .../datafaker/providers/base/BaseFaker.java | 8 ++-- .../net/datafaker/providers/base/Compass.java | 3 ++ .../datafaker/providers/base/Credentials.java | 11 ++++- .../net/datafaker/providers/base/File.java | 4 +- .../datafaker/providers/base/Internet.java | 13 +++--- .../net/datafaker/providers/base/Lorem.java | 8 ++-- .../net/datafaker/providers/base/Name.java | 1 - .../providers/base/ObjectMethods.java | 4 ++ .../net/datafaker/providers/base/Options.java | 1 + .../providers/base/ProviderRegistration.java | 2 + .../net/datafaker/providers/base/Twitter.java | 3 +- .../net/datafaker/providers/base/Vehicle.java | 8 ++-- .../providers/base/package-info.java | 6 +++ .../providers/entertainment/package-info.java | 6 +++ .../providers/food/package-info.java | 6 +++ .../providers/healthcare/package-info.java | 6 +++ .../net/datafaker/providers/package-info.java | 6 +++ .../providers/sport/package-info.java | 6 +++ .../providers/videogame/package-info.java | 6 +++ .../datafaker/sequence/FakeCollection.java | 7 +-- .../net/datafaker/sequence/FakeSequence.java | 6 ++- .../net/datafaker/sequence/FakeStream.java | 5 ++- .../net/datafaker/sequence/package-info.java | 6 +++ .../net/datafaker/service/FakeValues.java | 13 ++++-- .../datafaker/service/FakeValuesContext.java | 18 +++++--- .../datafaker/service/FakeValuesGrouping.java | 18 +++++--- .../service/FakeValuesInterface.java | 3 ++ .../datafaker/service/FakeValuesService.java | 34 ++++++++------ .../net/datafaker/service/FakerContext.java | 17 ++++--- .../net/datafaker/service/RandomService.java | 10 +++-- .../service/WeightedRandomSelector.java | 12 ++++- .../datafaker/service/files/package-info.java | 6 +++ .../net/datafaker/service/package-info.java | 6 +++ .../transformations/CompositeField.java | 8 +++- .../transformations/CsvTransformer.java | 3 +- .../net/datafaker/transformations/Field.java | 9 ++-- .../JavaObjectTransformer.java | 9 ++-- .../transformations/JsonTransformer.java | 11 ++--- .../transformations/SimpleField.java | 41 ++++++++--------- .../transformations/TomlTransformer.java | 16 ++++--- .../transformations/Transformer.java | 6 ++- .../transformations/XmlTransformer.java | 11 ++--- .../transformations/YamlTransformer.java | 7 +-- .../transformations/package-info.java | 6 +++ .../transformations/sql/SqlDialect.java | 8 ++-- .../transformations/sql/SqlTransformer.java | 17 ++++--- .../transformations/sql/package-info.java | 6 +++ .../datafaker/assertions/package-info.java | 6 +++ .../java/net/datafaker/formats/CsvTest.java | 2 +- .../java/net/datafaker/formats/JsonTest.java | 3 +- .../java/net/datafaker/formats/TomlTest.java | 3 +- .../java/net/datafaker/formats/YamlTest.java | 3 +- .../net/datafaker/formats/package-info.java | 6 +++ .../net/datafaker/helpers/package-info.java | 6 +++ .../integration/FakerIntegrationTest.java | 5 ++- .../integration/MostSpecificLocaleTest.java | 5 ++- .../datafaker/integration/package-info.java | 6 +++ .../script/ProvidersDocsGenerator.java | 7 +-- .../net/datafaker/script/package-info.java | 6 +++ .../service/FakeValuesServiceTest.java | 1 + 78 files changed, 461 insertions(+), 192 deletions(-) create mode 100644 src/main/java/net/datafaker/annotations/package-info.java create mode 100644 src/main/java/net/datafaker/idnumbers/package-info.java create mode 100644 src/main/java/net/datafaker/idnumbers/pt/br/package-info.java create mode 100644 src/main/java/net/datafaker/internal/helper/package-info.java create mode 100644 src/main/java/net/datafaker/package-info.java create mode 100644 src/main/java/net/datafaker/providers/base/package-info.java create mode 100644 src/main/java/net/datafaker/providers/entertainment/package-info.java create mode 100644 src/main/java/net/datafaker/providers/food/package-info.java create mode 100644 src/main/java/net/datafaker/providers/healthcare/package-info.java create mode 100644 src/main/java/net/datafaker/providers/package-info.java create mode 100644 src/main/java/net/datafaker/providers/sport/package-info.java create mode 100644 src/main/java/net/datafaker/providers/videogame/package-info.java create mode 100644 src/main/java/net/datafaker/sequence/package-info.java create mode 100644 src/main/java/net/datafaker/service/files/package-info.java create mode 100644 src/main/java/net/datafaker/service/package-info.java create mode 100644 src/main/java/net/datafaker/transformations/package-info.java create mode 100644 src/main/java/net/datafaker/transformations/sql/package-info.java create mode 100644 src/test/java/net/datafaker/assertions/package-info.java create mode 100644 src/test/java/net/datafaker/formats/package-info.java create mode 100644 src/test/java/net/datafaker/helpers/package-info.java create mode 100644 src/test/java/net/datafaker/integration/package-info.java create mode 100644 src/test/java/net/datafaker/script/package-info.java diff --git a/pom.xml b/pom.xml index b37f5e659..861b77581 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,8 @@ 4.3.0 6.0.3 9.0.24 + 1.0.0 + 2.42.0 17 17 3.15.0 @@ -89,6 +91,20 @@ ${libphonenumber.version} + + + org.jspecify + jspecify + ${jspecify.version} + provided + + + com.google.errorprone + error_prone_annotations + ${error_prone.version} + provided + + org.junit.jupiter diff --git a/src/main/java/net/datafaker/annotations/FakeResolver.java b/src/main/java/net/datafaker/annotations/FakeResolver.java index 933f039a6..9a7f21631 100644 --- a/src/main/java/net/datafaker/annotations/FakeResolver.java +++ b/src/main/java/net/datafaker/annotations/FakeResolver.java @@ -8,6 +8,7 @@ import net.datafaker.internal.helper.CopyOnWriteMap; import net.datafaker.transformations.JavaObjectTransformer; import net.datafaker.transformations.Schema; +import org.jspecify.annotations.Nullable; import static java.util.Objects.requireNonNull; @@ -24,12 +25,14 @@ private FakeResolver(Class clazz) { this.clazz = clazz; } + @SuppressWarnings("unchecked") public static FakeResolver of(Class clazz) { var fakeFactory = CLASS_2_FAKE_RESOLVER.computeIfAbsent(clazz, k -> new FakeResolver<>(clazz)); return (FakeResolver) fakeFactory; } - public T generate(Schema schema) { + @SuppressWarnings("unchecked") + public T generate(@Nullable Schema schema) { if (schema == null) { return generateFromDefaultSchema(); } @@ -37,6 +40,7 @@ public T generate(Schema schema) { return (T) JAVA_OBJECT_TRANSFORMER.apply(clazz, schema); } + @SuppressWarnings("unchecked") private T generateFromDefaultSchema() { Schema useSchema = DEFAULT_SCHEMA_CACHE.computeIfAbsent(clazz, (__) -> { FakeForSchema fakeForSchemaAnnotation = checkFakeAnnotation(clazz); @@ -46,28 +50,27 @@ private T generateFromDefaultSchema() { return (T) JAVA_OBJECT_TRANSFORMER.apply(clazz, useSchema); } + @SuppressWarnings("unchecked") private Schema getSchema(String pathToSchema) { - if (pathToSchema != null) { - try { - // indexOf() is faster than indexOf() since it has jvm intrinsic - final int sharpIndex = pathToSchema.indexOf("#"); - final Class classToCall; - final String methodName; - if (sharpIndex >= 0) { - classToCall = Class.forName(pathToSchema.substring(0, sharpIndex)); - methodName = pathToSchema.substring(sharpIndex + 1); - } else { - classToCall = this.clazz.getEnclosingClass(); - methodName = pathToSchema; - } - Method myStaticMethod = classToCall.getMethod(methodName); - myStaticMethod.setAccessible(true); - return (Schema) myStaticMethod.invoke(null); - } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException(e); + requireNonNull(pathToSchema, "The path to the schema is empty."); + + try { + // indexOf() is faster than indexOf() since it has jvm intrinsic + final int sharpIndex = pathToSchema.indexOf("#"); + final Class classToCall; + final String methodName; + if (sharpIndex >= 0) { + classToCall = Class.forName(pathToSchema.substring(0, sharpIndex)); + methodName = pathToSchema.substring(sharpIndex + 1); + } else { + classToCall = this.clazz.getEnclosingClass(); + methodName = pathToSchema; } - } else { - throw new IllegalArgumentException("The path to the schema is empty."); + Method myStaticMethod = classToCall.getMethod(methodName); + myStaticMethod.setAccessible(true); + return (Schema) myStaticMethod.invoke(null); + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); } } diff --git a/src/main/java/net/datafaker/annotations/package-info.java b/src/main/java/net/datafaker/annotations/package-info.java new file mode 100644 index 000000000..2224ab87b --- /dev/null +++ b/src/main/java/net/datafaker/annotations/package-info.java @@ -0,0 +1,7 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.annotations; + +import com.google.errorprone.annotations.CheckReturnValue; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java b/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java index 55176fe0f..9fffd828f 100644 --- a/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java @@ -13,7 +13,7 @@ import static net.datafaker.idnumbers.Utils.randomGender; /** - * Estonian personal identification number ("Isikukood" in estonian) + * Estonian personal identification number ("Isikukood" in Estonian) *

* The number is 11 digits, with modulus 11 checksum digit. * There is fixed list of valid first digits to signify gender and birth century diff --git a/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java b/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java index dc05cb666..d649b5abe 100644 --- a/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java @@ -64,12 +64,12 @@ private String basePart(BaseProviders faker, LocalDate birthday, PersonIdNumber. static int getCheckDigit(String basePart) { char[] numbers = basePart.toCharArray(); - int summ = 0; + int sum = 0; for (int i = 0; i < numbers.length; i++) { - summ += getNumericValue(numbers[i]) * (i + 1); + sum += getNumericValue(numbers[i]) * (i + 1); } - return summ % 11; + return sum % 11; } static int firstDigit(int birthYear, PersonIdNumber.Gender gender) { diff --git a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java index 89a59f73d..cc1daecd7 100644 --- a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java +++ b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java @@ -27,7 +27,7 @@ default String generateValid(BaseProviders faker) { String generateInvalid(BaseProviders faker); /** - * Generates a valid ID number for given country corresponding to given criterias (age range, gender etc.) + * Generates a valid ID number for given country corresponding to given criteria (age range, gender etc.) * * @return PersonIdNumber containing a valid combination of ID, Birthday and Gender. * In countries where ID number doesn't contain gender and/or birthday, the latter values are just random. diff --git a/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java b/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java index 5657f366c..958a70e7b 100644 --- a/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java @@ -4,6 +4,7 @@ import net.datafaker.providers.base.IdNumber.GenderRequest; import net.datafaker.providers.base.IdNumber.IdNumberRequest; import net.datafaker.providers.base.PersonIdNumber; +import org.jspecify.annotations.Nullable; import java.time.LocalDate; @@ -52,7 +53,7 @@ public String get(BaseProviders faker, LocalDate birthDate, Gender requestedGend return get(faker, birthDate, gender); } - private static PersonIdNumber.Gender pickGender(BaseProviders faker, Gender requestedGender) { + private static PersonIdNumber.Gender pickGender(BaseProviders faker, @Nullable Gender requestedGender) { return requestedGender == null ? randomGender(faker) : switch (requestedGender) { case ANY -> randomGender(faker); diff --git a/src/main/java/net/datafaker/idnumbers/package-info.java b/src/main/java/net/datafaker/idnumbers/package-info.java new file mode 100644 index 000000000..c1ad5824d --- /dev/null +++ b/src/main/java/net/datafaker/idnumbers/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.idnumbers; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/idnumbers/pt/br/IdNumberGeneratorPtBrUtil.java b/src/main/java/net/datafaker/idnumbers/pt/br/IdNumberGeneratorPtBrUtil.java index 6681621d0..d0f166f5d 100644 --- a/src/main/java/net/datafaker/idnumbers/pt/br/IdNumberGeneratorPtBrUtil.java +++ b/src/main/java/net/datafaker/idnumbers/pt/br/IdNumberGeneratorPtBrUtil.java @@ -108,7 +108,7 @@ public static boolean isCNPJValid(final String cnpj) { /** * Return true if the CPF is valid - * A valid CPF is unique and have a algorithm to validate it + * A valid CPF is unique and have an algorithm to validate it *

* CPF generator could generate a valid or invalid because, sometimes, we need to test a * registration with invalid number diff --git a/src/main/java/net/datafaker/idnumbers/pt/br/package-info.java b/src/main/java/net/datafaker/idnumbers/pt/br/package-info.java new file mode 100644 index 000000000..ad01eb8c8 --- /dev/null +++ b/src/main/java/net/datafaker/idnumbers/pt/br/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.idnumbers.pt.br; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/internal/helper/JavaNames.java b/src/main/java/net/datafaker/internal/helper/JavaNames.java index 3c75210e3..d42e248bc 100644 --- a/src/main/java/net/datafaker/internal/helper/JavaNames.java +++ b/src/main/java/net/datafaker/internal/helper/JavaNames.java @@ -1,5 +1,7 @@ package net.datafaker.internal.helper; +import org.jspecify.annotations.Nullable; + import static java.lang.Character.isLetter; import static java.lang.Character.toLowerCase; import static java.lang.Character.toUpperCase; @@ -8,7 +10,8 @@ import static net.datafaker.internal.helper.JavaNames.Transform.TO_UPPER; public class JavaNames { - public static String toJavaNames(String string, boolean isMethod) { + @Nullable + public static String toJavaNames(@Nullable String string, boolean isMethod) { if (string == null || string.isEmpty()) return string; int length = string.length(); diff --git a/src/main/java/net/datafaker/internal/helper/LazyEvaluated.java b/src/main/java/net/datafaker/internal/helper/LazyEvaluated.java index 0c11c9c68..0abb64b67 100644 --- a/src/main/java/net/datafaker/internal/helper/LazyEvaluated.java +++ b/src/main/java/net/datafaker/internal/helper/LazyEvaluated.java @@ -1,8 +1,13 @@ package net.datafaker.internal.helper; +import org.jspecify.annotations.Nullable; + import java.util.function.Supplier; +import static java.util.Objects.requireNonNull; + public class LazyEvaluated { + @Nullable private volatile T value; private final Supplier supplier; @@ -18,6 +23,6 @@ public T get() { } } } - return value; + return requireNonNull(value, "Null value not allowed"); } } diff --git a/src/main/java/net/datafaker/internal/helper/SingletonLocale.java b/src/main/java/net/datafaker/internal/helper/SingletonLocale.java index 8901b8bc5..cfbda740c 100644 --- a/src/main/java/net/datafaker/internal/helper/SingletonLocale.java +++ b/src/main/java/net/datafaker/internal/helper/SingletonLocale.java @@ -1,15 +1,17 @@ package net.datafaker.internal.helper; -import java.util.HashMap; +import org.jspecify.annotations.Nullable; + import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * This class allows to use {@link java.util.IdentityHashMap} for locales. * To do that it guarantees one instance of {@link SingletonLocale} per {@link Locale}. */ public class SingletonLocale { - private static final Map LOCALE2SINGLETON_LOCALE = new HashMap<>(); + private static final Map LOCALE2SINGLETON_LOCALE = new ConcurrentHashMap<>(); private final Locale locale; // Hash code is required for FakerContext where SingletonLocale is a field @@ -19,23 +21,16 @@ private SingletonLocale(Locale locale) { this.locale = locale; } - public static SingletonLocale get(Locale locale) { + @Nullable + public static SingletonLocale get(@Nullable Locale locale) { if (locale == null) { return null; } - SingletonLocale res = LOCALE2SINGLETON_LOCALE.get(locale); - if (res != null) { - return res; - } - synchronized (SingletonLocale.class) { - res = LOCALE2SINGLETON_LOCALE.get(locale); - if (res != null) { - return res; - } - res = new SingletonLocale(locale); - LOCALE2SINGLETON_LOCALE.put(locale, res); - return res; - } + return getRequired(locale); + } + + public static SingletonLocale getRequired(Locale locale) { + return LOCALE2SINGLETON_LOCALE.computeIfAbsent(locale, (__) -> new SingletonLocale(locale)); } public Locale getLocale() { diff --git a/src/main/java/net/datafaker/internal/helper/WordUtils.java b/src/main/java/net/datafaker/internal/helper/WordUtils.java index c2135aa5b..1b64f566a 100644 --- a/src/main/java/net/datafaker/internal/helper/WordUtils.java +++ b/src/main/java/net/datafaker/internal/helper/WordUtils.java @@ -1,10 +1,21 @@ package net.datafaker.internal.helper; +import org.jspecify.annotations.Nullable; public class WordUtils { - public static String capitalize(String input) { + /** + * @deprecated Use {@link #capitalizeWords(String)} instead - + * it doesn't accept nulls, and doesn't return nulls. + */ + @Deprecated + @Nullable + public static String capitalize(@Nullable String input) { if (input == null) return null; + return capitalizeWords(input); + } + + public static String capitalizeWords(String input) { if (input.isEmpty()) return input; final char ch0 = input.charAt(0); if (Character.isUpperCase(ch0)) return input; diff --git a/src/main/java/net/datafaker/internal/helper/package-info.java b/src/main/java/net/datafaker/internal/helper/package-info.java new file mode 100644 index 000000000..cb2e827cc --- /dev/null +++ b/src/main/java/net/datafaker/internal/helper/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.internal.helper; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/package-info.java b/src/main/java/net/datafaker/package-info.java new file mode 100644 index 000000000..7cf2ac36c --- /dev/null +++ b/src/main/java/net/datafaker/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/base/Animal.java b/src/main/java/net/datafaker/providers/base/Animal.java index 395d29536..ff52ff0da 100644 --- a/src/main/java/net/datafaker/providers/base/Animal.java +++ b/src/main/java/net/datafaker/providers/base/Animal.java @@ -1,6 +1,6 @@ package net.datafaker.providers.base; -import net.datafaker.internal.helper.WordUtils; +import static net.datafaker.internal.helper.WordUtils.capitalizeWords; /** * @since 0.8.0 @@ -20,7 +20,7 @@ public String scientificName() { } public String genus() { - return WordUtils.capitalize(faker.resolve("creature.animal.genus")); + return capitalizeWords(faker.resolve("creature.animal.genus")); } public String species() { diff --git a/src/main/java/net/datafaker/providers/base/Barcode.java b/src/main/java/net/datafaker/providers/base/Barcode.java index e75b040f7..b506ba6f4 100644 --- a/src/main/java/net/datafaker/providers/base/Barcode.java +++ b/src/main/java/net/datafaker/providers/base/Barcode.java @@ -55,9 +55,9 @@ private long ean(int length) { while (number > 0) { i++; if (i % 2 == 1) { - odd += number % 10; + odd += (int) (number % 10); } else { - even += number % 10; + even += (int) (number % 10); } number /= 10; diff --git a/src/main/java/net/datafaker/providers/base/BaseFaker.java b/src/main/java/net/datafaker/providers/base/BaseFaker.java index fe9553ca0..a5b655ef1 100644 --- a/src/main/java/net/datafaker/providers/base/BaseFaker.java +++ b/src/main/java/net/datafaker/providers/base/BaseFaker.java @@ -8,6 +8,7 @@ import net.datafaker.service.FakerContext; import net.datafaker.service.RandomService; import net.datafaker.transformations.Schema; +import org.jspecify.annotations.Nullable; import java.lang.reflect.Method; import java.net.URL; @@ -47,7 +48,7 @@ public BaseFaker(Random random) { this(Locale.ENGLISH, random); } - public BaseFaker(Locale locale, Random random) { + public BaseFaker(Locale locale, @Nullable Random random) { this(locale, new RandomService(random)); } @@ -59,7 +60,7 @@ public BaseFaker(FakeValuesService fakeValuesService, FakerContext context) { this(fakeValuesService, context, EVERY_PROVIDER_ALLOWED); } - public BaseFaker(FakeValuesService fakeValuesService, FakerContext context, Predicate> whiteListPredicate) { + public BaseFaker(FakeValuesService fakeValuesService, FakerContext context, @Nullable Predicate> whiteListPredicate) { this.fakeValuesService = fakeValuesService; this.context = context; this.whiteListPredicate = whiteListPredicate == null ? EVERY_PROVIDER_ALLOWED : whiteListPredicate; @@ -430,7 +431,8 @@ public final B getFaker() { return (B) this; } - public static Method getMethod(AbstractProvider ap, String methodName) { + @Nullable + public static Method getMethod(@Nullable AbstractProvider ap, String methodName) { return ap == null ? null : ObjectMethods.getMethodByName(ap, methodName); } diff --git a/src/main/java/net/datafaker/providers/base/Compass.java b/src/main/java/net/datafaker/providers/base/Compass.java index 50a750c15..ed3d1cae7 100644 --- a/src/main/java/net/datafaker/providers/base/Compass.java +++ b/src/main/java/net/datafaker/providers/base/Compass.java @@ -1,5 +1,7 @@ package net.datafaker.providers.base; +import org.jspecify.annotations.Nullable; + /** * @since 1.7.0 */ @@ -18,6 +20,7 @@ public enum CompassPoint { } } + @Nullable private CompassPoint compassPoint; protected Compass(BaseProviders faker) { diff --git a/src/main/java/net/datafaker/providers/base/Credentials.java b/src/main/java/net/datafaker/providers/base/Credentials.java index 0513279d9..294ebe675 100644 --- a/src/main/java/net/datafaker/providers/base/Credentials.java +++ b/src/main/java/net/datafaker/providers/base/Credentials.java @@ -1,8 +1,13 @@ package net.datafaker.providers.base; +import org.jspecify.annotations.Nullable; + +import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import static java.util.logging.Level.WARNING; + /** * Generates credentials such as usernames, uids and passwords. * @@ -12,6 +17,7 @@ public class Credentials extends AbstractProvider { public static final int MIN_PASSWORD_LENGTH = 8; public static final int MAX_PASSWORD_LENGTH = 16; + private static final Logger log = Logger.getLogger(Credentials.class.getName()); protected Credentials(BaseProviders faker) { super(faker); @@ -142,6 +148,7 @@ public String weakPassword() { * * @return A randomly generated user ID based on the regex or null if the regex is null or invalid */ + @Nullable public String userId() { return userId(resolve("credentials.uid_pattern")); } @@ -153,7 +160,8 @@ public String userId() { * @param regex The regex pattern to generate the user ID * @return A randomly generated user ID based on the regex or null if the regex is null or invalid */ - public String userId(String regex) { + @Nullable + public String userId(@Nullable String regex) { if(regex == null) { return null; } @@ -161,6 +169,7 @@ public String userId(String regex) { try { Pattern.compile(regex); } catch (PatternSyntaxException e) { + log.log(WARNING, e, () -> "Invalid regex pattern: " + regex); return null; } diff --git a/src/main/java/net/datafaker/providers/base/File.java b/src/main/java/net/datafaker/providers/base/File.java index 9fbef2baa..22c760dc9 100644 --- a/src/main/java/net/datafaker/providers/base/File.java +++ b/src/main/java/net/datafaker/providers/base/File.java @@ -1,5 +1,7 @@ package net.datafaker.providers.base; +import org.jspecify.annotations.Nullable; + import java.nio.file.FileSystems; /** @@ -23,7 +25,7 @@ public String fileName() { return fileName(null, null, null, null); } - public String fileName(String dirOrNull, String nameOrNull, String extensionOrNull, String separatorOrNull) { + public String fileName(@Nullable String dirOrNull, @Nullable String nameOrNull, @Nullable String extensionOrNull, @Nullable String separatorOrNull) { final String sep = separatorOrNull == null ? FileSystems.getDefault().getSeparator() : separatorOrNull; final String dir = dirOrNull == null ? faker.internet().slug() : dirOrNull; final String name = nameOrNull == null ? faker.lorem().word().toLowerCase(faker.getContext().getLocale()) : nameOrNull; diff --git a/src/main/java/net/datafaker/providers/base/Internet.java b/src/main/java/net/datafaker/providers/base/Internet.java index 73e4f9f11..8d2f96f6e 100644 --- a/src/main/java/net/datafaker/providers/base/Internet.java +++ b/src/main/java/net/datafaker/providers/base/Internet.java @@ -2,6 +2,7 @@ import net.datafaker.internal.helper.FakerIDN; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.net.Inet4Address; import java.net.Inet6Address; @@ -339,7 +340,7 @@ public int port(int from, int to) { * @param prefix a prefix to put on the front of the address * @return a correctly formatted MAC address */ - public String macAddress(String prefix) { + public String macAddress(@Nullable String prefix) { final String tmp = (prefix == null) ? "" : prefix; final int prefixLength = tmp.trim().isEmpty() ? 0 @@ -498,7 +499,7 @@ public String slug() { * @param glueOrNull if null, "_" * @return a slug string combining wordsOrNull with glueOrNull (ex. x_y) */ - public String slug(List wordsOrNull, String glueOrNull) { + public String slug(@Nullable List wordsOrNull, @Nullable String glueOrNull) { final String glue = glueOrNull == null ? "_" : glueOrNull; @@ -575,7 +576,7 @@ private T random(T[] src) { return src[faker.random().nextInt(src.length)]; } - public String userAgent(UserAgent userAgent) { + public String userAgent(@Nullable UserAgent userAgent) { UserAgent agent = userAgent; if (agent == null) { @@ -618,7 +619,7 @@ public String toString() { } } - public String botUserAgent(BotUserAgent vendor) { + public String botUserAgent(@Nullable BotUserAgent vendor) { BotUserAgent agent = vendor; if (agent == null) { @@ -648,9 +649,7 @@ public enum BotUserAgent { } private static BotUserAgent any(BaseProviders faker) { - BotUserAgent[] agents = BotUserAgent.values(); - int randomIndex = (int) (faker.random().nextDouble() * agents.length); - return agents[randomIndex]; + return faker.random().nextEnum(BotUserAgent.class); } @Override diff --git a/src/main/java/net/datafaker/providers/base/Lorem.java b/src/main/java/net/datafaker/providers/base/Lorem.java index fcffeb94d..45c187305 100644 --- a/src/main/java/net/datafaker/providers/base/Lorem.java +++ b/src/main/java/net/datafaker/providers/base/Lorem.java @@ -1,10 +1,10 @@ package net.datafaker.providers.base; -import net.datafaker.internal.helper.WordUtils; - import java.util.ArrayList; import java.util.List; +import static net.datafaker.internal.helper.WordUtils.capitalizeWords; + /** * @since 0.8.0 @@ -113,9 +113,9 @@ public String sentence(int wordCount) { public String sentence(int wordCount, int randomWordsToAdd) { int numberOfWordsToAdd = randomWordsToAdd == 0 ? 0 : faker.random().nextInt(randomWordsToAdd); final int totalWordCount = wordCount + numberOfWordsToAdd; - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(wordCount * 9); if (totalWordCount > 0) { - sb.append(WordUtils.capitalize(word())); + sb.append(capitalizeWords(word())); } for (int i = 1; i < totalWordCount; i++) { sb.append(" ").append(word()); diff --git a/src/main/java/net/datafaker/providers/base/Name.java b/src/main/java/net/datafaker/providers/base/Name.java index 71014e98d..93acc182c 100644 --- a/src/main/java/net/datafaker/providers/base/Name.java +++ b/src/main/java/net/datafaker/providers/base/Name.java @@ -152,7 +152,6 @@ public String title() { * @see Name#lastName() */ @Deprecated(since = "2.5.0", forRemoval = true) - @SuppressWarnings("removal") public String username() { return faker.credentials().username(); } diff --git a/src/main/java/net/datafaker/providers/base/ObjectMethods.java b/src/main/java/net/datafaker/providers/base/ObjectMethods.java index 2114b4324..69a6d06d7 100644 --- a/src/main/java/net/datafaker/providers/base/ObjectMethods.java +++ b/src/main/java/net/datafaker/providers/base/ObjectMethods.java @@ -1,5 +1,7 @@ package net.datafaker.providers.base; +import org.jspecify.annotations.Nullable; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.IdentityHashMap; @@ -44,11 +46,13 @@ public static Method getMethodByName(Object object, String methodName) { return METHODS_BY_NAME.computeIfAbsent(object.getClass(), ObjectMethods::scanMethodsByName).get(methodName); } + @Nullable private static Method getMethodByReturnType(Object object, String returnTypeSimpleName) { return METHODS_BY_RETURN_TYPE.computeIfAbsent(object.getClass(), ObjectMethods::scanMethodsByReturnType).get(returnTypeSimpleName); } @SuppressWarnings("unchecked") + @Nullable public static T executeMethodByReturnType(Object object, String returnTypeSimpleName) { try { Method method = getMethodByReturnType(object, returnTypeSimpleName); diff --git a/src/main/java/net/datafaker/providers/base/Options.java b/src/main/java/net/datafaker/providers/base/Options.java index 64032eb65..2d5035ea2 100644 --- a/src/main/java/net/datafaker/providers/base/Options.java +++ b/src/main/java/net/datafaker/providers/base/Options.java @@ -71,6 +71,7 @@ public final byte option(byte[] options) { * If size is zero then an empty subset will be returned. * If size is larger than a unique set from options then all options will be returned. */ + @SuppressWarnings("unchecked") public final Set subset(int size, E... options) { if (size < 0) { throw new IllegalArgumentException("size should be not negative: " + size); diff --git a/src/main/java/net/datafaker/providers/base/ProviderRegistration.java b/src/main/java/net/datafaker/providers/base/ProviderRegistration.java index c1068c57c..92e863e11 100644 --- a/src/main/java/net/datafaker/providers/base/ProviderRegistration.java +++ b/src/main/java/net/datafaker/providers/base/ProviderRegistration.java @@ -3,6 +3,7 @@ import net.datafaker.service.FakeValuesService; import net.datafaker.service.FakerContext; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.net.URL; import java.nio.file.Path; @@ -16,6 +17,7 @@ public interface ProviderRegistration { FakerContext getContext(); + @Nullable default > AP getProvider(String simpleClassName) { return ObjectMethods.executeMethodByReturnType(this, simpleClassName); } diff --git a/src/main/java/net/datafaker/providers/base/Twitter.java b/src/main/java/net/datafaker/providers/base/Twitter.java index 7e44091a4..c9c9fe142 100644 --- a/src/main/java/net/datafaker/providers/base/Twitter.java +++ b/src/main/java/net/datafaker/providers/base/Twitter.java @@ -1,6 +1,7 @@ package net.datafaker.providers.base; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.time.Instant; import java.time.ZoneId; @@ -100,7 +101,7 @@ public String twitterId(int expectedLength) { * @param wordMaxLength each word should be in range of the word max length. * @return a new fake text for the Twitter. */ - public String text(String[] keywords, int sentenceMaxLength, int wordMaxLength) { + public String text(String @Nullable [] keywords, int sentenceMaxLength, int wordMaxLength) { if (wordMaxLength <= 2) { LOGGER.warning("Word length less than 2 is dangerous. Exceptions can be raised."); } diff --git a/src/main/java/net/datafaker/providers/base/Vehicle.java b/src/main/java/net/datafaker/providers/base/Vehicle.java index 9fd4d71a3..7fb3edb6d 100644 --- a/src/main/java/net/datafaker/providers/base/Vehicle.java +++ b/src/main/java/net/datafaker/providers/base/Vehicle.java @@ -1,6 +1,8 @@ package net.datafaker.providers.base; +import org.jspecify.annotations.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -119,13 +121,13 @@ public String licensePlate() { return faker.bothify(faker.resolve("vehicle.license_plate")).toUpperCase(Locale.ROOT); } + @Nullable public String licensePlate(String stateAbbreviation) { - - if ("".equals(stateAbbreviation)) { + if (stateAbbreviation.isEmpty()) { return null; } String licensePlatesByState = resolve("vehicle.license_plate_by_state." + stateAbbreviation); - return licensePlatesByState == null ? null : faker.bothify(licensePlatesByState).toUpperCase(Locale.ROOT); + return faker.bothify(licensePlatesByState).toUpperCase(Locale.ROOT); } } diff --git a/src/main/java/net/datafaker/providers/base/package-info.java b/src/main/java/net/datafaker/providers/base/package-info.java new file mode 100644 index 000000000..4d902c9ec --- /dev/null +++ b/src/main/java/net/datafaker/providers/base/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.base; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/entertainment/package-info.java b/src/main/java/net/datafaker/providers/entertainment/package-info.java new file mode 100644 index 000000000..04db2d47e --- /dev/null +++ b/src/main/java/net/datafaker/providers/entertainment/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.entertainment; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/food/package-info.java b/src/main/java/net/datafaker/providers/food/package-info.java new file mode 100644 index 000000000..d7e785c35 --- /dev/null +++ b/src/main/java/net/datafaker/providers/food/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.food; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/healthcare/package-info.java b/src/main/java/net/datafaker/providers/healthcare/package-info.java new file mode 100644 index 000000000..aa94149b4 --- /dev/null +++ b/src/main/java/net/datafaker/providers/healthcare/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.healthcare; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/package-info.java b/src/main/java/net/datafaker/providers/package-info.java new file mode 100644 index 000000000..669e327a4 --- /dev/null +++ b/src/main/java/net/datafaker/providers/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/sport/package-info.java b/src/main/java/net/datafaker/providers/sport/package-info.java new file mode 100644 index 000000000..569e2636b --- /dev/null +++ b/src/main/java/net/datafaker/providers/sport/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.sport; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/providers/videogame/package-info.java b/src/main/java/net/datafaker/providers/videogame/package-info.java new file mode 100644 index 000000000..8c65690aa --- /dev/null +++ b/src/main/java/net/datafaker/providers/videogame/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.providers.videogame; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/sequence/FakeCollection.java b/src/main/java/net/datafaker/sequence/FakeCollection.java index cd0daf8fd..bdcfe1a68 100644 --- a/src/main/java/net/datafaker/sequence/FakeCollection.java +++ b/src/main/java/net/datafaker/sequence/FakeCollection.java @@ -1,6 +1,7 @@ package net.datafaker.sequence; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Iterator; @@ -16,9 +17,9 @@ private FakeCollection(List> suppliers, int minLength, int maxLength @Override @SuppressWarnings("unchecked") - public List get() { + public List<@Nullable T> get() { int size = randomService.nextInt(minLength, maxLength); - List result = new ArrayList<>(size); + List<@Nullable T> result = new ArrayList<>(size); while (result.size() < size) { result.add(singleton()); } @@ -26,7 +27,7 @@ public List get() { } @Override - public Iterator iterator() { + public Iterator<@Nullable T> iterator() { return get().iterator(); } diff --git a/src/main/java/net/datafaker/sequence/FakeSequence.java b/src/main/java/net/datafaker/sequence/FakeSequence.java index 451d4ccb7..a030d7a8f 100644 --- a/src/main/java/net/datafaker/sequence/FakeSequence.java +++ b/src/main/java/net/datafaker/sequence/FakeSequence.java @@ -1,7 +1,9 @@ package net.datafaker.sequence; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import net.datafaker.providers.base.BaseProviders; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Supplier; @@ -27,6 +29,7 @@ public boolean isInfinite() { return false; } + @Nullable public T singleton() { if (nullRate == 0d || randomService.nextDouble() >= nullRate) { return suppliers.get(randomService.nextInt(suppliers.size())).get(); @@ -39,7 +42,7 @@ public static abstract class Builder { protected int minLength = -1; protected int maxLength = -1; protected double nullRate = 0d; - protected BaseProviders faker; + protected @Nullable BaseProviders faker; protected Builder() { suppliers = new ArrayList<>(); @@ -64,6 +67,7 @@ public FakeSequence.Builder minLen(int minLength) { return this; } + @CanIgnoreReturnValue public FakeSequence.Builder maxLen(int maxLength) { this.maxLength = maxLength; return this; diff --git a/src/main/java/net/datafaker/sequence/FakeStream.java b/src/main/java/net/datafaker/sequence/FakeStream.java index d2bead484..a31334910 100644 --- a/src/main/java/net/datafaker/sequence/FakeStream.java +++ b/src/main/java/net/datafaker/sequence/FakeStream.java @@ -1,6 +1,7 @@ package net.datafaker.sequence; import net.datafaker.service.RandomService; +import org.jspecify.annotations.Nullable; import java.util.Iterator; import java.util.List; @@ -14,7 +15,7 @@ private FakeStream(List> suppliers, int minLength, int maxLength, Ra @Override @SuppressWarnings("unchecked") - public Stream get() { + public Stream<@Nullable T> get() { if (isInfinite()) { return Stream.generate(this::singleton); } @@ -29,7 +30,7 @@ public boolean isInfinite() { } @Override - public Iterator iterator() { + public Iterator<@Nullable T> iterator() { return get().iterator(); } diff --git a/src/main/java/net/datafaker/sequence/package-info.java b/src/main/java/net/datafaker/sequence/package-info.java new file mode 100644 index 000000000..4f9c913c4 --- /dev/null +++ b/src/main/java/net/datafaker/sequence/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.sequence; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/service/FakeValues.java b/src/main/java/net/datafaker/service/FakeValues.java index 269f20263..439b11505 100644 --- a/src/main/java/net/datafaker/service/FakeValues.java +++ b/src/main/java/net/datafaker/service/FakeValues.java @@ -1,6 +1,7 @@ package net.datafaker.service; import net.datafaker.internal.helper.LazyEvaluated; +import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.Yaml; import java.io.IOException; @@ -29,11 +30,13 @@ static FakeValues of(FakeValuesContext fakeValuesContext) { return FAKE_VALUES_MAP.computeIfAbsent(fakeValuesContext, FakeValues::new); } + @Nullable @Override public Map get(String key) { return getMap(values.get(), key); } + @Nullable private Map loadFromUrl() { final URL url = fakeValuesContext.getUrl(); if (url == null) { @@ -81,7 +84,7 @@ private Map loadValues() { return emptyMap(); } - private void enrichMapWithJavaNames(Map result) { + private void enrichMapWithJavaNames(@Nullable Map result) { if (result != null) { Map map = null; for (Map.Entry entry : result.entrySet()) { @@ -110,7 +113,8 @@ private void enrichMapWithJavaNames(Map result) { } } - private Map readFromStream(InputStream stream) { + @Nullable + private Map readFromStream(@Nullable InputStream stream) { if (stream == null) return null; final Map valuesMap = new Yaml().loadAs(stream, Map.class); Map localeBased = getMap(valuesMap, fakeValuesContext.getLocale().getLanguage()); @@ -120,18 +124,21 @@ private Map readFromStream(InputStream stream) { return getMap(localeBased, "faker"); } + @Nullable @SuppressWarnings("unchecked") private static Map getMap(Map map, String key) { return (Map) map.get(key); } + @Nullable Set getPaths() { return fakeValuesContext.getPath() != null ? Set.of(fakeValuesContext.getPath()) : keysOf(values.get()); } - private static Set keysOf(Map map) { + @Nullable + private static Set keysOf(@Nullable Map map) { return map == null || map.isEmpty() ? null : map.keySet(); } diff --git a/src/main/java/net/datafaker/service/FakeValuesContext.java b/src/main/java/net/datafaker/service/FakeValuesContext.java index c24540957..3ae14de32 100644 --- a/src/main/java/net/datafaker/service/FakeValuesContext.java +++ b/src/main/java/net/datafaker/service/FakeValuesContext.java @@ -1,18 +1,21 @@ package net.datafaker.service; import net.datafaker.internal.helper.SingletonLocale; +import org.jspecify.annotations.Nullable; import java.net.URISyntaxException; import java.net.URL; import java.util.Locale; import java.util.Objects; +import static java.util.Objects.requireNonNull; + class FakeValuesContext { private final SingletonLocale singletonLocale; - private final String filename; + private @Nullable final String filename; private final int filenameHashCode; - private final String path; - private final URL url; + private @Nullable final String path; + private @Nullable final URL url; private final int urlHashCode; private FakeValuesContext(Locale locale) { @@ -27,8 +30,8 @@ private FakeValuesContext(Locale locale, String filename, String path) { this(locale, filename, path, null); } - private FakeValuesContext(Locale locale, String filename, String path, URL url) { - this.singletonLocale = SingletonLocale.get(locale); + private FakeValuesContext(Locale locale, @Nullable String filename, @Nullable String path, @Nullable URL url) { + this.singletonLocale = requireNonNull(SingletonLocale.get(locale), () -> "Unsupported locale " + locale); this.filename = filename; this.path = path; this.url = url; @@ -84,14 +87,17 @@ public Locale getLocale() { return singletonLocale.getLocale(); } + @Nullable public String getFilename() { return filename; } + @Nullable String getPath() { return path; } + @Nullable public URL getUrl() { return url; } @@ -111,7 +117,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = singletonLocale == null ? 0 : singletonLocale.hashCode(); + int result = singletonLocale.hashCode(); result = 31 * result + filenameHashCode; result = 31 * result + (path == null ? 0 : path.hashCode()); result = 31 * result + urlHashCode; diff --git a/src/main/java/net/datafaker/service/FakeValuesGrouping.java b/src/main/java/net/datafaker/service/FakeValuesGrouping.java index 48ad17f5f..452e03bcb 100644 --- a/src/main/java/net/datafaker/service/FakeValuesGrouping.java +++ b/src/main/java/net/datafaker/service/FakeValuesGrouping.java @@ -1,14 +1,16 @@ package net.datafaker.service; import net.datafaker.service.files.EnFile; +import org.jspecify.annotations.Nullable; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; +import static java.util.Collections.emptyList; + public final class FakeValuesGrouping implements FakeValuesInterface { private static final FakeValuesGrouping ENGLISH_FAKE_VALUE_GROUPING = new FakeValuesGrouping(); private final Map> fakeValues = new HashMap<>(); @@ -38,16 +40,18 @@ public void add(FakeValuesInterface fakeValue) { } } + @Nullable @Override - @SuppressWarnings({"unchecked", "rawtypes"}) - public Map get(String key) { - Map result = null; - for (FakeValuesInterface fakeValues : fakeValues.getOrDefault(key, Collections.emptyList())) { + public Map get(String key) { + Map result = null; + for (FakeValuesInterface fakeValues : fakeValues.getOrDefault(key, emptyList())) { if (result == null) { result = fakeValues.get(key); } else { - final Map newResult = fakeValues.get(key); - result.putAll(newResult); + Map newResult = fakeValues.get(key); + if (newResult != null) { + result.putAll(newResult); + } } } return result; diff --git a/src/main/java/net/datafaker/service/FakeValuesInterface.java b/src/main/java/net/datafaker/service/FakeValuesInterface.java index cc4ba5e6b..0633fcd43 100644 --- a/src/main/java/net/datafaker/service/FakeValuesInterface.java +++ b/src/main/java/net/datafaker/service/FakeValuesInterface.java @@ -1,7 +1,10 @@ package net.datafaker.service; +import org.jspecify.annotations.Nullable; + import java.util.Map; public interface FakeValuesInterface { + @Nullable Map get(String key); } diff --git a/src/main/java/net/datafaker/service/FakeValuesService.java b/src/main/java/net/datafaker/service/FakeValuesService.java index 506daa85a..924fd47ae 100644 --- a/src/main/java/net/datafaker/service/FakeValuesService.java +++ b/src/main/java/net/datafaker/service/FakeValuesService.java @@ -3,7 +3,6 @@ import com.github.curiousoddman.rgxgen.RgxGen; import net.datafaker.internal.helper.CopyOnWriteMap; import net.datafaker.internal.helper.SingletonLocale; -import net.datafaker.internal.helper.WordUtils; import net.datafaker.providers.base.AbstractProvider; import net.datafaker.providers.base.Address; import net.datafaker.providers.base.BaseFaker; @@ -15,6 +14,7 @@ import net.datafaker.transformations.JsonTransformer; import net.datafaker.transformations.Schema; import net.datafaker.transformations.SimpleField; +import org.jspecify.annotations.Nullable; import java.io.IOException; import java.lang.reflect.Array; @@ -50,6 +50,7 @@ import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; import static net.datafaker.internal.helper.JavaNames.toJavaNames; +import static net.datafaker.internal.helper.WordUtils.capitalizeWords; import static net.datafaker.transformations.Field.field; public class FakeValuesService { @@ -61,7 +62,7 @@ public class FakeValuesService { public static final Supplier> MAP_STRING_STRING_SUPPLIER = () -> new CopyOnWriteMap<>(() -> new WeakHashMap<>()); private final Map fakeValuesInterfaceMap = new CopyOnWriteMap<>(IdentityHashMap::new); - public static final SingletonLocale DEFAULT_LOCALE = SingletonLocale.get(Locale.ENGLISH); + public static final SingletonLocale DEFAULT_LOCALE = SingletonLocale.getRequired(Locale.ENGLISH); private static final Map, Map>> CLASS_2_METHODS_CACHE = new CopyOnWriteMap<>(IdentityHashMap::new); private static final Map, Constructor> CLASS_2_CONSTRUCTOR_CACHE = new CopyOnWriteMap<>(IdentityHashMap::new); @@ -105,7 +106,7 @@ private FakeValuesInterface getCachedFakeValue(SingletonLocale locale) { * @param path path to a file with YAML structure * @throws IllegalArgumentException in case of invalid path */ - public void addPath(Locale locale, Path path) { + public void addPath(Locale locale, @Nullable Path path) { requireNonNull(locale); if (path == null || Files.notExists(path) || Files.isDirectory(path) || !Files.isReadable(path)) { throw new IllegalArgumentException("Path should be an existing readable file: \"%s\"".formatted(path)); @@ -125,10 +126,8 @@ public void addPath(Locale locale, Path path) { * @throws IllegalArgumentException in case of invalid url */ public void addUrl(Locale locale, URL url) { - requireNonNull(locale); - if (url == null) { - throw new IllegalArgumentException("url should be an existing readable file"); - } + requireNonNull(locale, "locale is required"); + requireNonNull(url, "url should be an existing readable file"); final FakeValues fakeValues = FakeValues.of(FakeValuesContext.of(locale, url)); final SingletonLocale sLocale = SingletonLocale.get(locale); fakeValuesInterfaceMap.merge(sLocale, fakeValues, @@ -143,6 +142,7 @@ public void addUrl(Locale locale, URL url) { /** * Fetch a random value from an array item specified by the key */ + @Nullable public Object fetch(String key, FakerContext context) { List valuesArray = null; final Object o = fetchObject(key, context); @@ -163,6 +163,7 @@ public Object fetch(String key, FakerContext context) { /** * Same as {@link #fetch(String, FakerContext)} but casts the result to a String */ + @Nullable public String fetchString(String key, FakerContext context) { return (String) fetch(key, context); } @@ -176,6 +177,7 @@ private SafeFetchResolver(String simpleDirective, FakerContext context) { this.context = context; } + @Nullable @Override public Object resolve() { return safeFetch(simpleDirective, context, null); @@ -203,8 +205,9 @@ public String toString() { * @param defaultIfNull the value to return if the fetched value is null * @return see above */ + @Nullable @SuppressWarnings("unchecked") - public String safeFetch(String key, FakerContext context, String defaultIfNull) { + public String safeFetch(String key, FakerContext context, @Nullable String defaultIfNull) { Object o = fetchObject(key, context); String str; if (o == null) return defaultIfNull; @@ -229,6 +232,7 @@ public String safeFetch(String key, FakerContext context, String defaultIfNull) * @param key key contains path to an object. Path segment is separated by * dot. E.g. name.first_name */ + @Nullable @SuppressWarnings("unchecked") public T fetchObject(String key, FakerContext context) { Object result = null; @@ -302,7 +306,7 @@ public T fetchObject(String key, FakerContext context) { sb = new StringBuilder(); } sb.append(itemStr, start, startWord); - sb.append(WordUtils.capitalize(path[0])).append(".").append(toJavaNames(itemStr.substring(startWord, j), true)).append("}"); + sb.append(capitalizeWords(path[0])).append(".").append(toJavaNames(itemStr.substring(startWord, j), true)).append("}"); start = j + 1; } } @@ -502,7 +506,7 @@ public String resolve(String key, AbstractProvider provider, FakerContext con *

* #{Person.hello_someone} will result in a method call to person.helloSomeone(); */ - public String resolve(String key, Object current, final ProviderRegistration root, Supplier exceptionMessage, FakerContext context) { + public String resolve(String key, Object current, @Nullable ProviderRegistration root, Supplier exceptionMessage, FakerContext context) { String expression; if (root == null) { expression = key2Expression @@ -513,7 +517,7 @@ public String resolve(String key, Object current, final ProviderRegistration roo } if (expression == null) { - throw new RuntimeException(exceptionMessage.get()); + throw new IllegalArgumentException(exceptionMessage.get()); } return resolveExpression(expression, current, root, context); @@ -618,7 +622,7 @@ public String jsona(String... fieldExpressions) { * Recursive templates are supported. if "#{x}" resolves to "#{Address.streetName}" then "#{x}" resolves to * {@link BaseFaker#address()}'s {@link Address#streetName()}. */ - protected String resolveExpression(String expression, Object current, ProviderRegistration root, FakerContext context) { + protected String resolveExpression(String expression, @Nullable Object current, @Nullable ProviderRegistration root, FakerContext context) { // indexOf() is faster than indexOf() since it has jvm intrinsic if (!expression.contains("}")) { return expression; @@ -1190,14 +1194,16 @@ public String toString() { } } - private record RegExpContext(String exp, ProviderRegistration root, FakerContext context) { + private record RegExpContext(String exp, @Nullable ProviderRegistration root, FakerContext context) { } private interface ValueResolver { + @Nullable Object resolve(); } - private record ConstantResolver(String value) implements ValueResolver { + private record ConstantResolver(@Nullable String value) implements ValueResolver { + @Nullable @Override public Object resolve() { return value; diff --git a/src/main/java/net/datafaker/service/FakerContext.java b/src/main/java/net/datafaker/service/FakerContext.java index 2f6db1fa4..babc3da01 100644 --- a/src/main/java/net/datafaker/service/FakerContext.java +++ b/src/main/java/net/datafaker/service/FakerContext.java @@ -11,6 +11,7 @@ import java.util.regex.Pattern; import static java.util.Locale.ROOT; +import static java.util.Objects.requireNonNull; import static net.datafaker.service.FakeValuesService.DEFAULT_LOCALE; /** @@ -62,13 +63,13 @@ public class FakerContext { * */ public FakerContext(Locale locale, RandomService randomService) { - this.sLocale = SingletonLocale.get(locale); + setLocale(locale); this.randomService = randomService; setCurrentLocale(locale); } - public void setLocale(Locale locale) { - this.sLocale = SingletonLocale.get(locale); + public final void setLocale(Locale locale) { + this.sLocale = singletonLocale(locale); } public void setRandomService(RandomService randomService) { @@ -129,8 +130,8 @@ private SingletonLocale normalizeLocale(SingletonLocale singletonLocale) { } public final void setCurrentLocale(Locale locale) { - Objects.requireNonNull(locale); - this.sLocale = normalizeLocale(SingletonLocale.get(locale)); + requireNonNull(locale); + this.sLocale = normalizeLocale(singletonLocale(locale)); if (LOCALE_2_LOCALES_CHAIN.containsKey(this.sLocale)) { return; } @@ -151,7 +152,7 @@ protected List localeChain(Locale from) { return DEFAULT_SINGLETON_LOCALE_LIST; } - return calculateLocaleChain(normalizeLocale(SingletonLocale.get(from))); + return calculateLocaleChain(normalizeLocale(singletonLocale(from))); } protected List localeChain() { @@ -203,4 +204,8 @@ public int hashCode() { public String toString() { return "FakerContext{%s, %s}".formatted(sLocale, randomService); } + + private static SingletonLocale singletonLocale(Locale locale) { + return requireNonNull(SingletonLocale.get(locale), () -> "Unsupported locale " + locale); + } } diff --git a/src/main/java/net/datafaker/service/RandomService.java b/src/main/java/net/datafaker/service/RandomService.java index 8e51939ac..5d020ced7 100644 --- a/src/main/java/net/datafaker/service/RandomService.java +++ b/src/main/java/net/datafaker/service/RandomService.java @@ -1,9 +1,13 @@ package net.datafaker.service; +import org.jspecify.annotations.Nullable; + import java.util.Objects; import java.util.Random; import java.util.random.RandomGenerator; +import static java.util.Objects.requireNonNullElse; + public class RandomService { private static final char[] HEX_UP = "0123456789ABCDEF".toCharArray(); private static final char[] HEX_LOWER = "0123456789abcdef".toCharArray(); @@ -20,8 +24,8 @@ public RandomService() { /** * @param random If null is passed in, a default Random is assigned */ - public RandomService(RandomGenerator random) { - this.random = random != null ? random : SHARED_RANDOM; + public RandomService(@Nullable RandomGenerator random) { + this.random = requireNonNullElse(random, SHARED_RANDOM); } @SuppressWarnings("unused") @@ -158,7 +162,7 @@ public boolean equals(Object o) { @Override public int hashCode() { if (random == SHARED_RANDOM) return 1; - return random != null ? random.hashCode() : 0; + return random.hashCode(); } @Override diff --git a/src/main/java/net/datafaker/service/WeightedRandomSelector.java b/src/main/java/net/datafaker/service/WeightedRandomSelector.java index 1557617e8..100ec5805 100644 --- a/src/main/java/net/datafaker/service/WeightedRandomSelector.java +++ b/src/main/java/net/datafaker/service/WeightedRandomSelector.java @@ -1,5 +1,7 @@ package net.datafaker.service; +import org.jspecify.annotations.Nullable; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -7,6 +9,8 @@ import java.util.Random; import java.util.Set; +import static java.util.Objects.requireNonNullElseGet; + /** * A utility class for selecting a random element from a list based on assigned weights. **/ @@ -14,8 +18,8 @@ public record WeightedRandomSelector(Random random) { private static final String WEIGHT_KEY = "weight"; private static final String VALUE_KEY = "value"; - public WeightedRandomSelector(Random random) { - this.random = random != null ? random : new Random(); + public WeightedRandomSelector(@Nullable Random random) { + this.random = requireNonNullElseGet(random, () -> new Random()); } /** @@ -46,6 +50,7 @@ public T select(List> items) { return selectWeightedElement(randomValue, cumulativeWeights, values); } + @SuppressWarnings("ConstantValue") private static void validateItemsList(List> items) { if (items == null) { throw new IllegalArgumentException("Input list cannot be null"); @@ -69,6 +74,7 @@ private static void assertUniqueValues(Map item, Set val } } + @SuppressWarnings("ConstantValue") private static void validateItem(Map item) { if (item == null) { throw new IllegalArgumentException("Item cannot be null"); @@ -83,6 +89,7 @@ private static void validateItem(Map item) { validateWeight(item.get(WEIGHT_KEY)); } + @SuppressWarnings("ConstantValue") private static void validateValue(Object valueObj) { if (valueObj == null) { throw new IllegalArgumentException("Value cannot be null"); @@ -123,6 +130,7 @@ static double[] preprocessItems(List> items, Object[] values return cumulativeWeights; } + @SuppressWarnings("unchecked") static T selectWeightedElement(double randomValue, double[] cumulativeWeights, Object[] values) { int index = Arrays.binarySearch(cumulativeWeights, randomValue); index = (index < 0) ? -index - 1 : index; diff --git a/src/main/java/net/datafaker/service/files/package-info.java b/src/main/java/net/datafaker/service/files/package-info.java new file mode 100644 index 000000000..9956934ca --- /dev/null +++ b/src/main/java/net/datafaker/service/files/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.service.files; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/service/package-info.java b/src/main/java/net/datafaker/service/package-info.java new file mode 100644 index 000000000..fa4438eed --- /dev/null +++ b/src/main/java/net/datafaker/service/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.service; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/transformations/CompositeField.java b/src/main/java/net/datafaker/transformations/CompositeField.java index 539e0e633..acf081490 100644 --- a/src/main/java/net/datafaker/transformations/CompositeField.java +++ b/src/main/java/net/datafaker/transformations/CompositeField.java @@ -1,24 +1,28 @@ package net.datafaker.transformations; import net.datafaker.providers.base.AbstractProvider; +import org.jspecify.annotations.Nullable; import java.util.Objects; public class CompositeField, MyType> extends Schema implements Field { + @Nullable private final String name; - public CompositeField(String name, Field[] fields) { + public CompositeField(@Nullable String name, Field[] fields) { super(fields); this.name = name; } + @Nullable @Override public String getName() { return name; } + @Nullable @Override - public MyType transform(MyObject input) { + public MyType transform(@Nullable MyObject input) { return null; } diff --git a/src/main/java/net/datafaker/transformations/CsvTransformer.java b/src/main/java/net/datafaker/transformations/CsvTransformer.java index be3c5b91a..123564444 100644 --- a/src/main/java/net/datafaker/transformations/CsvTransformer.java +++ b/src/main/java/net/datafaker/transformations/CsvTransformer.java @@ -1,6 +1,7 @@ package net.datafaker.transformations; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; import java.util.Iterator; @@ -60,7 +61,7 @@ public String generate(Iterable input, Schema schema) { return sb.toString(); } - private void addLine(StringBuilder sb, Object transform) { + private void addLine(StringBuilder sb, @Nullable Object transform) { if (transform instanceof CharSequence) { addCharSequence(sb, (CharSequence) transform); } else { diff --git a/src/main/java/net/datafaker/transformations/Field.java b/src/main/java/net/datafaker/transformations/Field.java index eff38af27..ff2de839b 100644 --- a/src/main/java/net/datafaker/transformations/Field.java +++ b/src/main/java/net/datafaker/transformations/Field.java @@ -1,14 +1,17 @@ package net.datafaker.transformations; import net.datafaker.providers.base.AbstractProvider; +import org.jspecify.annotations.Nullable; import java.util.function.Function; import java.util.function.Supplier; public interface Field { + @Nullable String getName(); - OUT transform(IN input); + @Nullable + OUT transform(@Nullable IN input); static SimpleField field( String name, Function transform) { @@ -16,13 +19,13 @@ static SimpleField field( } static SimpleField field( - String name, Supplier supplier) { + @Nullable String name, Supplier<@Nullable MyType> supplier) { return new SimpleField<>(name, supplier); } static , MyType> CompositeField compositeField( - String name, Field[] fields) { + @Nullable String name, Field[] fields) { return new CompositeField<>(name, fields); } diff --git a/src/main/java/net/datafaker/transformations/JavaObjectTransformer.java b/src/main/java/net/datafaker/transformations/JavaObjectTransformer.java index de9934216..19c24969d 100644 --- a/src/main/java/net/datafaker/transformations/JavaObjectTransformer.java +++ b/src/main/java/net/datafaker/transformations/JavaObjectTransformer.java @@ -1,6 +1,7 @@ package net.datafaker.transformations; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; import java.io.OutputStream; import java.lang.reflect.Constructor; @@ -26,10 +27,10 @@ public class JavaObjectTransformer implements Transformer { @Override public Object apply(Object input, Schema schema) { - Class clazz; + Class clazz; Object result = null; if (input instanceof Class) { - clazz = (Class) input; + clazz = (Class) input; } else { clazz = input.getClass(); result = input; @@ -119,7 +120,7 @@ public Collection generate(Iterable input, Schema sch return collection; } - public JavaObjectTransformer from(Class input) { + public JavaObjectTransformer from(Class input) { sourceClazz = Optional.of(input); return this; } @@ -163,7 +164,7 @@ public String getEndStream() { private Object getObject(Schema schema, Object result, Constructor recordConstructor) { final Field[] fields = schema.getFields(); - final Object[] values = new Object[fields.length]; + final @Nullable Object[] values = new Object[fields.length]; for (int i = 0; i < fields.length; i++) { values[i] = fields[i].transform(result); } diff --git a/src/main/java/net/datafaker/transformations/JsonTransformer.java b/src/main/java/net/datafaker/transformations/JsonTransformer.java index 3afbf1995..e473f8158 100644 --- a/src/main/java/net/datafaker/transformations/JsonTransformer.java +++ b/src/main/java/net/datafaker/transformations/JsonTransformer.java @@ -1,6 +1,7 @@ package net.datafaker.transformations; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; import java.math.BigDecimal; import java.math.BigInteger; @@ -88,9 +89,9 @@ public String getElementSeparator() { return ","; } - private void applyValue(IN input, StringBuilder sb, Object value) { + private void applyValue(IN input, StringBuilder sb, @Nullable Object value) { if (value instanceof Collection) { - sb.append(generate(input, (Collection) value)); + sb.append(generate(input, (Collection) value)); } else if (value != null && value.getClass().isArray()) { sb.append(generate(input, Arrays.asList((Object[]) value))); } else { @@ -98,7 +99,7 @@ private void applyValue(IN input, StringBuilder sb, Object value) { } } - private String generate(IN input, Collection collection) { + private String generate(IN input, Collection collection) { StringBuilder sb = new StringBuilder(); sb.append("["); int i = 0; @@ -108,7 +109,7 @@ private String generate(IN input, Collection collection) { } i++; if (value instanceof CompositeField) { - sb.append(apply(input, ((CompositeField) value))); + sb.append(apply(input, (CompositeField) value)); } else { applyValue(input, sb, value); } @@ -117,7 +118,7 @@ private String generate(IN input, Collection collection) { return sb.toString(); } - private static void value2String(Object value, StringBuilder sb) { + private static void value2String(@Nullable Object value, StringBuilder sb) { if (value == null) { sb.append("null"); } else if (value instanceof Integer diff --git a/src/main/java/net/datafaker/transformations/SimpleField.java b/src/main/java/net/datafaker/transformations/SimpleField.java index fed8caddf..5bf67f2f3 100644 --- a/src/main/java/net/datafaker/transformations/SimpleField.java +++ b/src/main/java/net/datafaker/transformations/SimpleField.java @@ -1,52 +1,53 @@ package net.datafaker.transformations; +import org.jspecify.annotations.Nullable; + import java.util.Objects; import java.util.function.Function; import java.util.function.Supplier; -public class SimpleField implements Field { - private final String name; - private final Function transform; - private final Supplier supplier; +import static java.util.Objects.requireNonNull; - protected SimpleField(String name, Function transform) { - this(name, transform, null); - } +public class SimpleField implements Field { + private final @Nullable String name; + private final @Nullable Function transform; + private final @Nullable Supplier supplier; - protected SimpleField(String name, Supplier supplier) { - this(name, null, supplier); + protected SimpleField(@Nullable String name, Function transform) { + this.name = name; + this.transform = requireNonNull(transform); + this.supplier = null; } - private SimpleField(String name, Function transform, Supplier supplier) { + protected SimpleField(@Nullable String name, Supplier supplier) { this.name = name; - this.transform = transform; - this.supplier = supplier; - if (this.transform == null && this.supplier == null) { - throw new IllegalArgumentException("Either transform or supplier should be non-null"); - } + this.transform = null; + this.supplier = requireNonNull(supplier); } + @Nullable @Override public String getName() { return name; } + @Nullable @Override - public MyType transform(MyObject input) { + public MyType transform(@Nullable MyObject input) { if (transform == null) { - return supplier.get(); - } - if (input == null) { - throw new IllegalArgumentException("Input could be null only if suppliers are defined"); + return requireNonNull(supplier).get(); } + requireNonNull(input, "Input could be null only if suppliers are defined"); return transform.apply(input); } + @Nullable public Function getTransform() { return transform; } + @Nullable public Supplier getSupplier() { return supplier; } diff --git a/src/main/java/net/datafaker/transformations/TomlTransformer.java b/src/main/java/net/datafaker/transformations/TomlTransformer.java index 7a0c7cea0..a6477c2cb 100644 --- a/src/main/java/net/datafaker/transformations/TomlTransformer.java +++ b/src/main/java/net/datafaker/transformations/TomlTransformer.java @@ -1,13 +1,19 @@ package net.datafaker.transformations; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; import java.util.stream.Collectors; public class TomlTransformer implements Transformer { @Override - public String apply(IN input, Schema schema) { + public String apply(@Nullable IN input, @Nullable Schema schema) { if (schema == null) { return ""; } @@ -55,9 +61,9 @@ public String getEndStream() { return ""; } - private void writeSchema(StringBuilder sb, IN input, Schema schema, String pathPrefix) { + private void writeSchema(StringBuilder sb, @Nullable IN input, Schema schema, String pathPrefix) { Field[] fields = schema.getFields(); - Set seen = new HashSet<>(); + Set<@Nullable String> seen = new HashSet<>(); for (Field f : fields) { String key = f.getName(); if (!seen.add(key)) { @@ -68,7 +74,7 @@ private void writeSchema(StringBuilder sb, IN input, Schema schema, Strin } } - private void writeValue(StringBuilder sb, String key, String pathPrefix, Object val) { + private void writeValue(StringBuilder sb, @Nullable String key, String pathPrefix, @Nullable Object val) { String fullPath = pathPrefix.isEmpty() ? key : pathPrefix + "." + key; if (val instanceof Schema nested) { diff --git a/src/main/java/net/datafaker/transformations/Transformer.java b/src/main/java/net/datafaker/transformations/Transformer.java index 651f1926b..e948d8a87 100644 --- a/src/main/java/net/datafaker/transformations/Transformer.java +++ b/src/main/java/net/datafaker/transformations/Transformer.java @@ -1,5 +1,7 @@ package net.datafaker.transformations; +import org.jspecify.annotations.Nullable; + import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -8,9 +10,9 @@ public interface Transformer { String LINE_SEPARATOR = System.lineSeparator(); - OUT apply(IN input, Schema schema); + OUT apply(@Nullable IN input, Schema schema); - default OUT apply(IN input, Schema schema, long rowId) { + default OUT apply(@Nullable IN input, Schema schema, long rowId) { // ignore rowId by default return apply(input, schema); } diff --git a/src/main/java/net/datafaker/transformations/XmlTransformer.java b/src/main/java/net/datafaker/transformations/XmlTransformer.java index 5bb52a9aa..87651306d 100644 --- a/src/main/java/net/datafaker/transformations/XmlTransformer.java +++ b/src/main/java/net/datafaker/transformations/XmlTransformer.java @@ -6,6 +6,7 @@ import java.util.StringJoiner; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; public class XmlTransformer implements Transformer { @@ -21,7 +22,7 @@ private XmlTransformer(boolean pretty) { } @Override - public CharSequence apply(IN input, Schema schema) { + public CharSequence apply(@Nullable IN input, Schema schema) { StringBuilder sb = new StringBuilder(); Arrays.stream(schema.getFields()).forEach(it -> apply(input, sb, it)); return sb.toString(); @@ -77,7 +78,7 @@ public XmlTransformer build() { } } - private void apply(IN input, StringBuilder sb, Field xmlNode) { + private void apply(@Nullable IN input, StringBuilder sb, Field xmlNode) { if (pretty && tagIndex > 0) { sb.append(System.lineSeparator()).append(offset(tagIndex)); @@ -97,7 +98,7 @@ private void apply(IN input, StringBuilder sb, Field xmlNode) { applyTag(input, sb, xmlNode, tag); } - private void applyTag(IN input, StringBuilder sb, Field field, String tag) { + private void applyTag(@Nullable IN input, StringBuilder sb, @Nullable Field field, String tag) { if (field == null ) { applyValue(sb, tag, null); return; @@ -127,7 +128,7 @@ private void applyTag(IN input, StringBuilder sb, Field field, String tag } } - private boolean isAttribute(String name) { + private boolean isAttribute(@Nullable String name) { return name != null; } @@ -141,7 +142,7 @@ private void applyAttributes(IN input, StringBuilder sb, Field[] attrs) { } } - private void applyValue(StringBuilder sb, String tag, String xmlNodeValue) { + private void applyValue(StringBuilder sb, String tag, @Nullable String xmlNodeValue) { if (xmlNodeValue != null) { sb.append(">"); sb.append(escape(xmlNodeValue)); diff --git a/src/main/java/net/datafaker/transformations/YamlTransformer.java b/src/main/java/net/datafaker/transformations/YamlTransformer.java index 80f0dea5c..49e670db7 100644 --- a/src/main/java/net/datafaker/transformations/YamlTransformer.java +++ b/src/main/java/net/datafaker/transformations/YamlTransformer.java @@ -7,13 +7,14 @@ import java.util.StringJoiner; import net.datafaker.sequence.FakeSequence; +import org.jspecify.annotations.Nullable; public class YamlTransformer implements Transformer { private static final String INDENTATION = " "; @Override - public CharSequence apply(IN input, Schema schema) { + public CharSequence apply(@Nullable IN input, Schema schema) { Field[] fields = schema.getFields(); if (fields.length == 0) { @@ -59,7 +60,7 @@ public String getEndStream() { return ""; } - private String apply(final StringBuilder sb, final IN input, final Field[] fields, final String offset) { + private String apply(final StringBuilder sb, @Nullable IN input, final Field[] fields, final String offset) { Set keys = new HashSet<>(); for (Field field : fields) { String key = field.getName().trim(); @@ -88,7 +89,7 @@ private void addCollection(StringBuilder sb, Collection collection, Stri sb.append(System.lineSeparator()); } } - private void value2String(Object value, StringBuilder sb, String offset) { + private void value2String(@Nullable Object value, StringBuilder sb, String offset) { if (value instanceof Schema) { Field[] fields = ((Schema) value).getFields(); apply(sb, null, fields, offset); diff --git a/src/main/java/net/datafaker/transformations/package-info.java b/src/main/java/net/datafaker/transformations/package-info.java new file mode 100644 index 000000000..9f8e343c1 --- /dev/null +++ b/src/main/java/net/datafaker/transformations/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.transformations; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java index aa3e1e16e..f3a7d6d36 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java @@ -1,5 +1,7 @@ package net.datafaker.transformations.sql; +import org.jspecify.annotations.Nullable; + import java.util.function.Function; import java.util.function.Supplier; @@ -111,17 +113,17 @@ public Casing getUnquotedCasing() { return unquotedCasing; } - public static String getFirstRow(SqlDialect dialect, Supplier input, Supplier input2, SqlTransformer.Case keywordCase) { + public static String getFirstRow(@Nullable SqlDialect dialect, Supplier input, Supplier input2, SqlTransformer.Case keywordCase) { return dialect == null ? DEFAULT_FIRST_ROW.apply(input, input2, keywordCase) : dialect.batchFirstRow.apply(input, input2, keywordCase); } - public static String getOtherRow(SqlDialect dialect, Supplier input, Supplier input2, SqlTransformer.Case keywordCase) { + public static String getOtherRow(@Nullable SqlDialect dialect, Supplier input, Supplier input2, SqlTransformer.Case keywordCase) { return dialect == null ? DEFAULT_OTHER_ROWS.apply(input, input2, keywordCase) : dialect.batchOtherRows.apply(input, input2, keywordCase); } - public static String getLastRowSuffix(SqlDialect dialect, SqlTransformer.Case caze) { + public static String getLastRowSuffix(@Nullable SqlDialect dialect, SqlTransformer.Case caze) { return dialect == null ? "" : dialect.lastBatchRow.apply(caze); } diff --git a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java index 697212ed3..7d7e7bf08 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java @@ -7,6 +7,7 @@ import net.datafaker.transformations.Schema; import net.datafaker.transformations.SimpleField; import net.datafaker.transformations.Transformer; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -34,20 +35,20 @@ public class SqlTransformer implements Transformer { private final char openSqlIdentifier; private final char closeSqlIdentifier; private final String tableName; - private final String schemaName; + private final @Nullable String schemaName; private final boolean withBatchMode; private final int batchSize; private final Case keywordCase; private final boolean forceSqlQuoteIdentifierUsage; - private final SqlDialect dialect; + private final @Nullable SqlDialect dialect; public static SqlTransformer.SqlTransformerBuilder builder() { return new SqlTransformer.SqlTransformerBuilder<>(); } - private SqlTransformer(String schemaName, String tableName, char quote, SqlDialect dialect, String sqlIdentifier, + private SqlTransformer(String schemaName, String tableName, char quote, @Nullable SqlDialect dialect, String sqlIdentifier, Casing casing, boolean withBatchMode, int batchSize, Case keywordCase, boolean forceSqlQuoteIdentifierUsage) { this.schemaName = schemaName; this.quote = quote; @@ -194,7 +195,7 @@ private String handleObjectInMap(Map map) { return result.toString(); } - private String handleObject(Object value) { + private String handleObject(@Nullable Object value) { if (value == null) { return NULL.getValue(keywordCase); } else { @@ -307,7 +308,7 @@ private String appendTableInfo(Field[] fields) { return result.toString(); } - private void appendNameToQuery(StringBuilder sb, String name) { + private void appendNameToQuery(StringBuilder sb, @Nullable String name) { if (name == null || name.isEmpty()) return; boolean sqlIdentifierRequired = isSqlQuoteIdentifierRequiredFor(name); @@ -375,7 +376,7 @@ public String getEndStream() { throw new UnsupportedOperationException("Not supported for SQL transformer. Use generate or generateStream instead to create SQL statements."); } - private String generateBatchModeStatements(Schema schema, List inputs, int limit) { + private String generateBatchModeStatements(Schema schema, @Nullable List inputs, int limit) { StringBuilder sb = new StringBuilder(); limit = inputs != null ? Math.min(limit, inputs.size()) : limit; for (int i = 0; i < limit; i++) { @@ -452,9 +453,7 @@ public static class SqlTransformerBuilder { private int batchSize = -1; // no limit private Case keywordCase = Case.UPPERCASE; private boolean forceSqlQuoteIdentifierUsage = false; - - - private SqlDialect dialect; + private @Nullable SqlDialect dialect; public SqlTransformerBuilder dialect(SqlDialect dialect) { this.dialect = dialect; diff --git a/src/main/java/net/datafaker/transformations/sql/package-info.java b/src/main/java/net/datafaker/transformations/sql/package-info.java new file mode 100644 index 000000000..79ae388e8 --- /dev/null +++ b/src/main/java/net/datafaker/transformations/sql/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.transformations.sql; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/assertions/package-info.java b/src/test/java/net/datafaker/assertions/package-info.java new file mode 100644 index 000000000..0ff46b006 --- /dev/null +++ b/src/test/java/net/datafaker/assertions/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.assertions; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/formats/CsvTest.java b/src/test/java/net/datafaker/formats/CsvTest.java index d0092790a..8546ec4b8 100644 --- a/src/test/java/net/datafaker/formats/CsvTest.java +++ b/src/test/java/net/datafaker/formats/CsvTest.java @@ -322,7 +322,7 @@ void supplierShouldBeDefinedInCaseOfNullInput() { .header(false).separator(" : ") .build() .generate(schema, 1)) - .isInstanceOf(IllegalArgumentException.class) + .isInstanceOf(NullPointerException.class) .hasMessageContaining("Input could be null only if suppliers are defined"); } } diff --git a/src/test/java/net/datafaker/formats/JsonTest.java b/src/test/java/net/datafaker/formats/JsonTest.java index d48f51d4a..7f6b2b547 100644 --- a/src/test/java/net/datafaker/formats/JsonTest.java +++ b/src/test/java/net/datafaker/formats/JsonTest.java @@ -6,6 +6,7 @@ import net.datafaker.transformations.Field; import net.datafaker.transformations.JsonTransformer; import net.datafaker.transformations.Schema; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -212,7 +213,7 @@ private static Stream generateTestSchema() { of(Schema.of(field("boolean", () -> true)), "{\"boolean\": true}"), of(Schema.of(field("nullValue", () -> null)), "{\"nullValue\": null}"), of( - Schema.of(field("array", () -> new String[]{null, "test", "123"})), + Schema.of(field("array", () -> new @Nullable String[]{null, "test", "123"})), "{\"array\": [null, \"test\", \"123\"]}"), of( Schema.of(field("array", () -> new Integer[]{123, 456, 789})), diff --git a/src/test/java/net/datafaker/formats/TomlTest.java b/src/test/java/net/datafaker/formats/TomlTest.java index 2d1a4f52c..26af746b9 100644 --- a/src/test/java/net/datafaker/formats/TomlTest.java +++ b/src/test/java/net/datafaker/formats/TomlTest.java @@ -4,6 +4,7 @@ import net.datafaker.providers.base.Name; import net.datafaker.transformations.Schema; import net.datafaker.transformations.TomlTransformer; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -39,7 +40,7 @@ private static Stream generateTestSchema() { of(Schema.of(field("number", () -> BigDecimal.valueOf(123.123))), "number = 123.123"), of(Schema.of(field("boolean", () -> true)), "boolean = true"), of(Schema.of(field("nullValue", () -> null)), "nullValue = null"), - of(Schema.of(field("array", () -> new String[]{null, "test", "123"})), + of(Schema.of(field("array", () -> new @Nullable String[]{null, "test", "123"})), "array = [ null, \"test\", \"123\" ]"), of(Schema.of(field("array", () -> new Integer[]{123, 456, 789})), "array = [ 123, 456, 789 ]"), diff --git a/src/test/java/net/datafaker/formats/YamlTest.java b/src/test/java/net/datafaker/formats/YamlTest.java index f0c4c3742..e199e9752 100644 --- a/src/test/java/net/datafaker/formats/YamlTest.java +++ b/src/test/java/net/datafaker/formats/YamlTest.java @@ -4,6 +4,7 @@ import net.datafaker.providers.base.Name; import net.datafaker.transformations.Schema; import net.datafaker.transformations.YamlTransformer; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -41,7 +42,7 @@ private static Stream generateTestSchema() { of(Schema.of(field("number", () -> BigDecimal.valueOf(123.123))), "number: 123.123" + System.lineSeparator()), of(Schema.of(field("boolean", () -> true)), "boolean: true" + System.lineSeparator()), of(Schema.of(field("nullValue", () -> null)), "nullValue: null" + System.lineSeparator()), - of(Schema.of(field("array", () -> new String[]{null, "test", "123"})), + of(Schema.of(field("array", () -> new @Nullable String[]{null, "test", "123"})), "array:" + System.lineSeparator() + " - null" + System.lineSeparator() + " - test" + System.lineSeparator() diff --git a/src/test/java/net/datafaker/formats/package-info.java b/src/test/java/net/datafaker/formats/package-info.java new file mode 100644 index 000000000..2d45b2754 --- /dev/null +++ b/src/test/java/net/datafaker/formats/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.formats; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/helpers/package-info.java b/src/test/java/net/datafaker/helpers/package-info.java new file mode 100644 index 000000000..998f8281b --- /dev/null +++ b/src/test/java/net/datafaker/helpers/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.helpers; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/integration/FakerIntegrationTest.java b/src/test/java/net/datafaker/integration/FakerIntegrationTest.java index 8e0768bd3..23b6d8c56 100644 --- a/src/test/java/net/datafaker/integration/FakerIntegrationTest.java +++ b/src/test/java/net/datafaker/integration/FakerIntegrationTest.java @@ -6,6 +6,7 @@ import net.datafaker.providers.base.App; import net.datafaker.providers.base.BaseFaker; import net.datafaker.providers.base.Name; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -74,7 +75,7 @@ public static SkippedMethods of(Class clazz, String... methodNames) { } } - private Faker init(Locale locale, Random random) { + private Faker init(@Nullable Locale locale, @Nullable Random random) { if (locale != null && random != null) { return new Faker(locale, random); } else if (locale != null) { @@ -88,7 +89,7 @@ private Faker init(Locale locale, Random random) { @ParameterizedTest @MethodSource("dataParameters") - void testAllFakerMethodsThatReturnStrings(Locale locale, Random random) throws Exception { + void testAllFakerMethodsThatReturnStrings(@Nullable Locale locale, @Nullable Random random) throws Exception { log.fine(() -> " (%s, %s)".formatted(locale, random)); final Faker faker = init(locale, random); diff --git a/src/test/java/net/datafaker/integration/MostSpecificLocaleTest.java b/src/test/java/net/datafaker/integration/MostSpecificLocaleTest.java index 07fcb6335..f00868913 100644 --- a/src/test/java/net/datafaker/integration/MostSpecificLocaleTest.java +++ b/src/test/java/net/datafaker/integration/MostSpecificLocaleTest.java @@ -2,6 +2,7 @@ import net.datafaker.service.FakeValuesService; import net.datafaker.service.FakerContext; +import net.datafaker.service.RandomService; import org.junit.jupiter.api.Test; import java.util.List; @@ -16,8 +17,8 @@ */ class MostSpecificLocaleTest { - private final FakerContext en = new FakerContext(new Locale("en"), null); - private final FakerContext en_US = new FakerContext(new Locale("en", "US"), null); + private final FakerContext en = new FakerContext(new Locale("en"), new RandomService()); + private final FakerContext en_US = new FakerContext(new Locale("en", "US"), new RandomService()); @Test void resolvesTheMostSpecificLocale() { diff --git a/src/test/java/net/datafaker/integration/package-info.java b/src/test/java/net/datafaker/integration/package-info.java new file mode 100644 index 000000000..096f156d8 --- /dev/null +++ b/src/test/java/net/datafaker/integration/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.integration; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/script/ProvidersDocsGenerator.java b/src/test/java/net/datafaker/script/ProvidersDocsGenerator.java index ca23ea4fd..311b14f28 100644 --- a/src/test/java/net/datafaker/script/ProvidersDocsGenerator.java +++ b/src/test/java/net/datafaker/script/ProvidersDocsGenerator.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.comments.JavadocComment; import com.google.common.base.Strings; import net.datafaker.providers.base.AbstractProvider; +import org.jspecify.annotations.Nullable; import org.reflections.Reflections; import java.io.BufferedWriter; @@ -191,10 +192,10 @@ private enum Column { SINCE("Since", 7, Pattern.compile("@since\\s+\\d\\.\\d+\\.\\d"), comment -> comment.group().substring("@since".length()).trim()); private final String columnName; private final int length; - private final Pattern pattern2extract; - private final Function extractor; + private final @Nullable Pattern pattern2extract; + private final @Nullable Function extractor; - Column(String columnName, int length, Pattern pattern2extract, Function extractor) { + Column(String columnName, int length, @Nullable Pattern pattern2extract, @Nullable Function extractor) { this.columnName = columnName; this.length = length; this.pattern2extract = pattern2extract; diff --git a/src/test/java/net/datafaker/script/package-info.java b/src/test/java/net/datafaker/script/package-info.java new file mode 100644 index 000000000..9b678c78d --- /dev/null +++ b/src/test/java/net/datafaker/script/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@CheckReturnValue +package net.datafaker.script; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/net/datafaker/service/FakeValuesServiceTest.java b/src/test/java/net/datafaker/service/FakeValuesServiceTest.java index 665c367cf..9374fc20b 100644 --- a/src/test/java/net/datafaker/service/FakeValuesServiceTest.java +++ b/src/test/java/net/datafaker/service/FakeValuesServiceTest.java @@ -210,6 +210,7 @@ void testLocalesChainGetterRu() { } @Test + @SuppressWarnings("deprecation") void testFakerContextSetLocale() { final FakerContext fakerContext = new FakerContext(new Locale("en"), new RandomService()); fakerContext.setLocale(new Locale("uk")); From b51ccfe8f3b5a91e749a0f7c3a9cff2474a28191 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Fri, 20 Feb 2026 10:47:52 +0200 Subject: [PATCH 2/7] remove unneeded @Nullable method `faker.credentials.userId()` never returns null. --- src/main/java/net/datafaker/providers/base/Credentials.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/datafaker/providers/base/Credentials.java b/src/main/java/net/datafaker/providers/base/Credentials.java index 294ebe675..b36692009 100644 --- a/src/main/java/net/datafaker/providers/base/Credentials.java +++ b/src/main/java/net/datafaker/providers/base/Credentials.java @@ -146,11 +146,11 @@ public String weakPassword() { * Generates a user ID based on the regex pattern defined in the resource file. * If the regex is null or invalid, it returns null. * - * @return A randomly generated user ID based on the regex or null if the regex is null or invalid + * @return A randomly generated user ID */ - @Nullable public String userId() { - return userId(resolve("credentials.uid_pattern")); + String regex = resolve("credentials.uid_pattern"); + return faker.regexify(regex); } /** From 65214ebc78162c174fe09f4e094360c9adfdcda8 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 24 Feb 2026 15:49:43 +0200 Subject: [PATCH 3/7] [code review] inline variable --- src/main/java/net/datafaker/providers/base/Credentials.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/net/datafaker/providers/base/Credentials.java b/src/main/java/net/datafaker/providers/base/Credentials.java index b36692009..68f47e258 100644 --- a/src/main/java/net/datafaker/providers/base/Credentials.java +++ b/src/main/java/net/datafaker/providers/base/Credentials.java @@ -149,8 +149,7 @@ public String weakPassword() { * @return A randomly generated user ID */ public String userId() { - String regex = resolve("credentials.uid_pattern"); - return faker.regexify(regex); + return faker.regexify(resolve("credentials.uid_pattern")); } /** From 59c3050cff1c7fb2c88dca69d9d7f0079c46b284 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 24 Feb 2026 15:55:02 +0200 Subject: [PATCH 4/7] [code review] revert unrelated changes (typos, other fixes) --- .../net/datafaker/idnumbers/HungarianIdNumber.java | 6 +++--- .../net/datafaker/idnumbers/IdNumberGenerator.java | 2 +- .../datafaker/internal/helper/SingletonLocale.java | 14 +++++++++++++- .../java/net/datafaker/providers/base/Barcode.java | 4 ++-- .../java/net/datafaker/providers/base/Lorem.java | 2 +- .../datafaker/service/FakeValuesServiceTest.java | 1 - 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java b/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java index d649b5abe..dc05cb666 100644 --- a/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/HungarianIdNumber.java @@ -64,12 +64,12 @@ private String basePart(BaseProviders faker, LocalDate birthday, PersonIdNumber. static int getCheckDigit(String basePart) { char[] numbers = basePart.toCharArray(); - int sum = 0; + int summ = 0; for (int i = 0; i < numbers.length; i++) { - sum += getNumericValue(numbers[i]) * (i + 1); + summ += getNumericValue(numbers[i]) * (i + 1); } - return sum % 11; + return summ % 11; } static int firstDigit(int birthYear, PersonIdNumber.Gender gender) { diff --git a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java index cc1daecd7..89a59f73d 100644 --- a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java +++ b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java @@ -27,7 +27,7 @@ default String generateValid(BaseProviders faker) { String generateInvalid(BaseProviders faker); /** - * Generates a valid ID number for given country corresponding to given criteria (age range, gender etc.) + * Generates a valid ID number for given country corresponding to given criterias (age range, gender etc.) * * @return PersonIdNumber containing a valid combination of ID, Birthday and Gender. * In countries where ID number doesn't contain gender and/or birthday, the latter values are just random. diff --git a/src/main/java/net/datafaker/internal/helper/SingletonLocale.java b/src/main/java/net/datafaker/internal/helper/SingletonLocale.java index cfbda740c..1e7fedf9c 100644 --- a/src/main/java/net/datafaker/internal/helper/SingletonLocale.java +++ b/src/main/java/net/datafaker/internal/helper/SingletonLocale.java @@ -30,7 +30,19 @@ public static SingletonLocale get(@Nullable Locale locale) { } public static SingletonLocale getRequired(Locale locale) { - return LOCALE2SINGLETON_LOCALE.computeIfAbsent(locale, (__) -> new SingletonLocale(locale)); + SingletonLocale res = LOCALE2SINGLETON_LOCALE.get(locale); + if (res != null) { + return res; + } + synchronized (SingletonLocale.class) { + res = LOCALE2SINGLETON_LOCALE.get(locale); + if (res != null) { + return res; + } + res = new SingletonLocale(locale); + LOCALE2SINGLETON_LOCALE.put(locale, res); + return res; + } } public Locale getLocale() { diff --git a/src/main/java/net/datafaker/providers/base/Barcode.java b/src/main/java/net/datafaker/providers/base/Barcode.java index b506ba6f4..e75b040f7 100644 --- a/src/main/java/net/datafaker/providers/base/Barcode.java +++ b/src/main/java/net/datafaker/providers/base/Barcode.java @@ -55,9 +55,9 @@ private long ean(int length) { while (number > 0) { i++; if (i % 2 == 1) { - odd += (int) (number % 10); + odd += number % 10; } else { - even += (int) (number % 10); + even += number % 10; } number /= 10; diff --git a/src/main/java/net/datafaker/providers/base/Lorem.java b/src/main/java/net/datafaker/providers/base/Lorem.java index 45c187305..29ce6b900 100644 --- a/src/main/java/net/datafaker/providers/base/Lorem.java +++ b/src/main/java/net/datafaker/providers/base/Lorem.java @@ -113,7 +113,7 @@ public String sentence(int wordCount) { public String sentence(int wordCount, int randomWordsToAdd) { int numberOfWordsToAdd = randomWordsToAdd == 0 ? 0 : faker.random().nextInt(randomWordsToAdd); final int totalWordCount = wordCount + numberOfWordsToAdd; - StringBuilder sb = new StringBuilder(wordCount * 9); + StringBuilder sb = new StringBuilder(); if (totalWordCount > 0) { sb.append(capitalizeWords(word())); } diff --git a/src/test/java/net/datafaker/service/FakeValuesServiceTest.java b/src/test/java/net/datafaker/service/FakeValuesServiceTest.java index 9374fc20b..665c367cf 100644 --- a/src/test/java/net/datafaker/service/FakeValuesServiceTest.java +++ b/src/test/java/net/datafaker/service/FakeValuesServiceTest.java @@ -210,7 +210,6 @@ void testLocalesChainGetterRu() { } @Test - @SuppressWarnings("deprecation") void testFakerContextSetLocale() { final FakerContext fakerContext = new FakerContext(new Locale("en"), new RandomService()); fakerContext.setLocale(new Locale("uk")); From 56d2c0850cd2841e0084fe0102338096fa4fd6b2 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 24 Feb 2026 16:28:50 +0200 Subject: [PATCH 5/7] [code review] mark `path` parameter as non-nullable --- src/main/java/net/datafaker/service/FakeValuesService.java | 3 ++- .../java/net/datafaker/providers/base/CustomFakerTest.java | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/datafaker/service/FakeValuesService.java b/src/main/java/net/datafaker/service/FakeValuesService.java index 924fd47ae..983501e89 100644 --- a/src/main/java/net/datafaker/service/FakeValuesService.java +++ b/src/main/java/net/datafaker/service/FakeValuesService.java @@ -106,8 +106,9 @@ private FakeValuesInterface getCachedFakeValue(SingletonLocale locale) { * @param path path to a file with YAML structure * @throws IllegalArgumentException in case of invalid path */ - public void addPath(Locale locale, @Nullable Path path) { + public void addPath(Locale locale, Path path) { requireNonNull(locale); + //noinspection ConstantValue if (path == null || Files.notExists(path) || Files.isDirectory(path) || !Files.isReadable(path)) { throw new IllegalArgumentException("Path should be an existing readable file: \"%s\"".formatted(path)); } diff --git a/src/test/java/net/datafaker/providers/base/CustomFakerTest.java b/src/test/java/net/datafaker/providers/base/CustomFakerTest.java index e2f4b76fe..cad3529d3 100644 --- a/src/test/java/net/datafaker/providers/base/CustomFakerTest.java +++ b/src/test/java/net/datafaker/providers/base/CustomFakerTest.java @@ -70,15 +70,18 @@ public String bee() { } @Test + @SuppressWarnings("DataFlowIssue") void addNullExistingPath() { assertThatThrownBy(() -> new BaseFaker().addPath(Locale.ENGLISH, null)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Path should be an existing readable file: \"null\""); } @Test void addNonExistingPath() { assertThatThrownBy(() -> new BaseFaker().addPath(Locale.ENGLISH, Paths.get("non-existing-file"))) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Path should be an existing readable file: \"non-existing-file\""); } @RepeatedTest(10) From 3af18e8a9a4baab5816e7e62335189a600411a35 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 24 Feb 2026 16:47:23 +0200 Subject: [PATCH 6/7] fixup: mark `generateSeparatedStatements()` param as nullable --- .../net/datafaker/transformations/sql/SqlTransformer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java index 7d7e7bf08..eea669204 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java @@ -78,12 +78,12 @@ private boolean isSqlQuoteIdentifierRequiredFor(String name) { } @Override - public CharSequence apply(IN input, Schema schema) { + public CharSequence apply(@Nullable IN input, Schema schema) { return apply(input, schema, 0); } @Override - public CharSequence apply(IN input, Schema schema, long rowId) { + public CharSequence apply(@Nullable IN input, Schema schema, long rowId) { //noinspection unchecked Field[] fields = (Field[]) schema.getFields(); if (fields.length == 0) { @@ -106,7 +106,7 @@ public CharSequence apply(IN input, Schema schema, long rowId) { } } - private String addValues(IN input, Field[] fields, Boolean isRoot) { + private String addValues(@Nullable IN input, Field[] fields, Boolean isRoot) { StringJoiner result = new StringJoiner(", "); for (int i = 0; i < fields.length; i++) { @@ -433,7 +433,7 @@ public Stream generateStream(final Schema schema, long limi } } - private String generateSeparatedStatements(Schema schema, List inputs, int limit) { + private String generateSeparatedStatements(Schema schema, @Nullable List inputs, int limit) { StringJoiner data = new StringJoiner(LINE_SEPARATOR); limit = inputs != null ? Math.min(limit, inputs.size()) : limit; for (int i = 0; i < limit; i++) { From 1362f0e01985594620e46ecbbd83c48a58fe480b Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Tue, 24 Feb 2026 17:22:54 +0200 Subject: [PATCH 7/7] [code review] remove unrelated comment from method `Credentials.userId()` without regex parameter --- src/main/java/net/datafaker/providers/base/Credentials.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/datafaker/providers/base/Credentials.java b/src/main/java/net/datafaker/providers/base/Credentials.java index 68f47e258..ea6d2532b 100644 --- a/src/main/java/net/datafaker/providers/base/Credentials.java +++ b/src/main/java/net/datafaker/providers/base/Credentials.java @@ -144,7 +144,6 @@ public String weakPassword() { /** * Generates a user ID based on the regex pattern defined in the resource file. - * If the regex is null or invalid, it returns null. * * @return A randomly generated user ID */