org.instancio.InstancioOperations Maven / Gradle / Ivy
/*
* Copyright 2022-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.instancio;
import org.instancio.exception.InstancioApiException;
import org.instancio.generator.AfterGenerate;
import org.instancio.generator.Generator;
import org.instancio.generator.GeneratorSpec;
import org.instancio.generators.Generators;
import org.instancio.settings.Keys;
import org.instancio.settings.SettingKey;
import org.instancio.settings.Settings;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* Defines top-level operations supported by the API.
*
* @param the type of object to create
* @since 4.0.0
*/
interface InstancioOperations {
/**
* Specifies that a class or field should be ignored.
*
* Example:
*
{@code
* Person person = Instancio.of(Person.class)
* .ignore(field(Phone::getPhoneNumber))
* .ignore(allStrings())
* .create();
* }
*
* will create a fully populated person, but will ignore the
* {@code getPhoneNumber} field, and all strings.
*
*
Precedence
*
* This method has higher precedence than other API methods.
* Once a target is ignored, no other selectors will apply.
* For example, the following snippet will trigger an unused selector error
* because {@code field(Phone::getNumber)} is redundant:
*
*
{@code
* Person person = Instancio.of(Person.class)
* .ignore(all(Phone.class))
* .set(field(Phone::getNumber), "123-45-56")
* .create();
* }
*
* Usage with Java records
*
* If {@code ignore()} targets one of the required arguments of a record
* constructor, then a default value for the ignored type will be generated.
*
*
Example:
*
{@code
* record PersonRecord(String name, int age) {}
*
* PersonRecord person = Instancio.of(PersonRecord.class)
* .ignore(allInts())
* .ignore(allStrings())
* .create();
*
* // will produce: PersonRecord[name=null, age=0]
* }
*
* @param selector for fields and/or classes this method should be applied to
* @return API builder reference
* @since 4.0.0
*/
InstancioOperations ignore(TargetSelector selector);
/**
* Specifies that a field or class is nullable. By default, Instancio assigns
* non-null values to fields. If marked as nullable, Instancio will generate either
* a null or non-null value.
*
* Example:
*
{@code
* Person person = Instancio.of(Person.class)
* .withNullable(allStrings())
* .withNullable(field(Person::getAddress))
* .withNullable(fields().named("lastModified"))
* .create();
* }
*
* Note: a type marked as nullable using this method is only nullable
* when declared as a field, but not as a collection element,
* or map key/value. For example, {@code withNullable(allStrings())}
* will not generate nulls in a {@code List}.
*
* @param selector for fields and/or classes this method should be applied to
* @return API builder reference
*/
InstancioOperations withNullable(TargetSelector selector);
/**
* Sets a value to matching selector targets.
*
* Example: if {@code Person} class contains a {@code List}, the following
* snippet will set all the country code of all phone instances to "+1".
* {@code
* Person person = Instancio.of(Person.class)
* .set(field(Phone::getCountryCode), "+1")
* .create();
* }
*
* Note: Instancio will not
*
* - populate or modify objects supplied by this method
* - apply other {@code set()}, {@code supply()}, or {@code generate()}}
* methods with matching selectors to the supplied object
* - invoke {@code onComplete()} callbacks on supplied instances
*
*
* @param selector for fields and/or classes this method should be applied to
* @param value value to set
* @param type of the value
* @return API builder reference
* @see #supply(TargetSelector, Supplier)
* @since 4.0.0
*/
InstancioOperations set(TargetSelector selector, V value);
/**
* Supplies an object using a {@link Supplier}.
*
* Example:
*
{@code
* Person person = Instancio.of(Person.class)
* .supply(all(LocalDateTime.class), () -> LocalDateTime.now())
* .supply(field(Address::getPhoneNumbers), () -> List.of(
* new PhoneNumber("+1", "123-45-67"),
* new PhoneNumber("+1", "345-67-89")))
* .create();
* }
*
* Note: Instancio will not
*
* - populate or modify objects supplied by this method
* - apply other {@code set()}, {@code supply()}, or {@code generate()}}
* methods with matching selectors to the supplied object
* - invoke {@code onComplete()} callbacks on supplied instances
*
*
* If you require the supplied object to be populated and/or selectors
* to be applied, use the {@link #supply(TargetSelector, Generator)} method instead.
*
* @param selector for fields and/or classes this method should be applied to
* @param supplier providing the value for given selector
* @param type of the supplied value
* @return API builder reference
* @see #supply(TargetSelector, Generator)
* @since 4.0.0
*/
InstancioOperations supply(TargetSelector selector, Supplier supplier);
/**
* Supplies an object using a {@link Generator} to matching selector targets.
* By default, Instancio will populate uninitialised fields of the supplied
* object. This includes fields with {@code null} or default primitive values.
*
* This method supports the following use cases.
*
*
Generate random objects
*
* This method provides an instance of {@link Random} that can be used
* to randomise generated objects. For example, if Instancio did not support
* creation of {@code java.time.Year}, it could be generated as follows:
*
*
{@code
* List years = Instancio.ofList(Year.class)
* .supply(all(Year.class), random -> Year.of(random.intRange(1900, 2000)))
* .create();
* }
*
* Provide a partially initialised instance
*
* In some cases, an object may need to be created in a certain state or
* instantiated using a specific constructor to be in a valid state.
* A partially initialised instance can be supplied using this method, and
* Instancio will populate remaining fields that are {@code null}:
*
*
{@code
* Person person = Instancio.of(Person.class)
* .supply(field(Person::getAddress), random -> new Address("Springfield", "USA"))
* .create();
* }
*
* This behaviour is controlled by the {@link AfterGenerate} hint specified
* by {@link Generator#hints()}. Refer to the {@link Generator#hints()} Javadoc
* for details, or
* Custom Generators section of the user guide.
*
* @param selector for fields and/or classes this method should be applied to
* @param generator that will provide the values
* @param type of the value to generate
* @return API builder reference
* @see Generator
* @see AfterGenerate
* @see Keys#AFTER_GENERATE_HINT
* @since 4.0.0
*/
InstancioOperations supply(TargetSelector selector, Generator generator);
/**
* Customises values using built-in generators provided by the {@code gen}
* parameter, of type {@link Generators}.
*
* Example:
*
{@code
* Person person = Instancio.of(Person.class)
* .generate(field(Person::getAge), gen -> gen.ints().range(18, 100))
* .generate(all(LocalDate.class), gen -> gen.temporal().localDate().past())
* .generate(field(Address::getPhoneNumbers), gen -> gen.collection().size(5))
* .generate(field(Address::getCity), gen -> gen.oneOf("Burnaby", "Vancouver", "Richmond"))
* .create();
* }
*
* @param selector for fields and/or classes this method should be applied to
* @param gen provider of customisable built-in generators (also known as specs)
* @param type of object to generate
* @return API builder reference
* @see #generate(TargetSelector, GeneratorSpec)
* @see Generators
*/
InstancioOperations generate(TargetSelector selector, GeneratorSpecProvider gen);
/**
* Customises values using arbitrary generator specs.
*
* Example:
*
{@code
* Person person = Instancio.of(Person.class)
* .generate(field(Person::getAge), Instancio.ints().range(18, 100))
* .generate(all(LocalDate.class), Instancio.temporal().localDate().past())
* .generate(field(Phone::getNumber), MyCustomGenerators.phones().northAmerican())
* .create();
* }
*
* @param selector for fields and/or classes this method should be applied to
* @param spec generator spec
* @param type of object to generate
* @return API builder reference
* @see #generate(TargetSelector, GeneratorSpecProvider)
* @since 4.0.0
*/
InstancioOperations generate(TargetSelector selector, GeneratorSpec spec);
/**
* A callback that gets invoked after an object has been fully populated.
*
* Example:
*
{@code
* // Sets countryCode field on all instances of Phone to the specified value
* Person person = Instancio.of(Person.class)
* .onComplete(all(Phone.class), (Phone phone) -> phone.setCountryCode("+1"))
* .create();
* }
*
* Note: callbacks are never invoked on objects provided using:
*
*
* - {@link #set(TargetSelector, Object)}
* - {@link #supply(TargetSelector, Supplier)}
*
*
* {@code
* Person person = Instancio.of(Person.class)
* .set(field(Phone::getCountryCode), "+1")
* .onComplete(field(Phone::getCountryCode), ...) // will not be invoked!
* .create();
* }
*
* @param selector for fields and/or classes this method should be applied to
* @param callback to invoke after object has been populated
* @param type of object handled by the callback
* @return API builder reference
* @since 4.0.0
*/
InstancioOperations onComplete(TargetSelector selector, OnCompleteCallback callback);
/**
* Maps target field or class to the given subtype. This can be used
* in the following cases:
*
*
* - to specify an implementation for interfaces or abstract classes
* - to override default implementations used by Instancio
*
* Specify an implementation for an abstract type
*
* When Instancio encounters an interface or an abstract type it is not aware of
* (for example, that is not part of the JDK), it will not be able to instantiate it.
* This method can be used to specify an implementation to use in such cases.
* For example:
*
*
{@code
* WidgetContainer container = Instancio.of(WidgetContainer.class)
* .subtype(all(AbstractWidget.class), ConcreteWidget.class)
* .create();
* }
*
* Override default implementations
*
* By default, Instancio uses certain defaults for collection classes, for example
* {@link ArrayList} for {@link List}.
* If an alternative implementation is required, this method allows to specify it:
*
*
{@code
* Person person = Instancio.of(Person.class)
* .subtype(all(List.class), LinkedList.class)
* .create();
* }
*
* will use the {@code LinkedList} implementation for all {@code List}s.
*
* @param selector for fields and/or classes this method should be applied to
* @param subtype to map the selector to
* @return API builder reference
* @since 4.0.0
*/
InstancioOperations subtype(TargetSelector selector, Class> subtype);
/**
* Generates values based on given assignments.
* An {@link Assignment} can be created using one of the builder patterns
* provided by the {@link Assign} class.
*
*
* - {@code Assign.valueOf(originSelector).to(destinationSelector)}
* - {@code Assign.given(originSelector).satisfies(predicate).set(destinationSelector, value)}
* - {@code Assign.given(originSelector, destinationSelector).set(predicate, value)}
*
*
* For example, the following snippet uses
* {@link Assign#given(TargetSelector, TargetSelector)} to create
* an assignment that sets {@code Phone.countryCode} based on
* the value of the {@code Address.country} field:
*
*
{@code
* Assignment assignment = Assign.given(field(Address::getCountry), field(Phone::getCountryCode))
* .set(When.isIn("Canada", "USA"), "+1")
* .set(When.is("Italy"), "+39")
* .set(When.is("Poland"), "+48")
* .set(When.is("Germany"), "+49");
*
* Person person = Instancio.of(Person.class)
* .generate(field(Address::getCountry), gen -> gen.oneOf("Canada", "USA", "Italy", "Poland", "Germany"))
* .assign(assignment)
* .create();
* }
*
* The above API allows specifying different values for a given
* origin/destination pair. An alternative for creating a conditional
* is provided by {@link Assign#given(TargetSelector)}. This method
* allows specifying different destination selectors for a given origin:
*
*
{@code
* Assignment shippedOrderAssignment = Assign.given(Order::getStatus)
* .is(OrderStatus.SHIPPED)
* .supply(field(Order::getDeliveryDueDate), () -> LocalDate.now().plusDays(2));
*
* Assignment cancelledOrderAssignment = Assign.given(Order::getStatus)
* .is(OrderStatus.CANCELLED)
* .set(field(Order::getCancellationReason), "Shipping delays")
* .generate(field(Order::getCancellationDate), gen -> gen.temporal().instant().past());
*
* List orders = Instancio.ofList(Order.class)
* .generate(all(OrderStatus.class), gen -> gen.oneOf(OrderStatus.SHIPPED, OrderStatus.CANCELLED))
* .assign(shippedOrderAssignment, cancelledOrderAssignment)
* .create();
* }
*
* Limitations of assignments
*
* Using assignments has a few limitations to be aware of.
*
*
* - The origin selector must match a single target.
* It must not be a {@link SelectorGroup} created via
* {@link Select#all(GroupableSelector...)} or primitive/wrapper
* selector, such as {@link Select#allInts()}
* - An assignment where the origin selector's target is within
* a collection element must have a destination selector
* within the same collection element.
* - Circular assignments will produce an error.
*
*
* @param assignments one or more assignment expressions for setting values
* @return API builder reference
* @throws InstancioApiException if the origin selector of an assignment
* matches more than one target, or the
* assignments form a cycle
* @see Assign
* @since 4.0.0
*/
InstancioOperations assign(Assignment... assignments);
/**
* Specifies the maximum depth for populating an object.
* The root object is at depth zero. Children of the root
* object are at depth 1, grandchildren at depth 2, and so on.
*
* Instancio will populate values up to the maximum depth.
* Beyond that, values will be {@code null} unless the maximum
* depth is set to a higher value.
*
*
The default maximum depth is defined by {@link Keys#MAX_DEPTH}.
*
*
Note: this method is a shorthand for:
*
*
{@code
* int maxDepth = 5;
* Person person = Instancio.of(Person.class)
* .withSettings(Settings.create().set(Keys.MAX_DEPTH, maxDepth))
* .create();
* }
*
* If the maximum depth is specified using {@code Settings} and
* this method, then this method takes precedence.
*
* @param maxDepth the maximum depth, must not be negative
* @return API builder reference
* @since 4.0.0
*/
InstancioOperations withMaxDepth(int maxDepth);
/**
* Override setting for the given {@code key} with the specified {@code value}.
*
* @param key the setting key to override
* @param value the setting value
* @param the setting value type
* @return API builder reference
* @see Keys
* @see #withSettings(Settings)
* @since 4.3.1
*/
InstancioOperations withSetting(SettingKey key, V value);
/**
* Override default {@link Settings} for generating values.
* The {@link Settings} class supports various parameters, such as
* collection sizes, string lengths, numeric ranges, and so on.
* For a list of overridable settings, refer to the {@link Keys} class.
*
* @param settings to use
* @return API builder reference
* @see Keys
* @see #withSetting(SettingKey, Object)
* @since 4.0.0
*/
InstancioOperations withSettings(Settings settings);
/**
* Sets the seed value for the random number generator. If the seed is not specified,
* a random seed will be used. Specifying the seed is useful for reproducing test results.
* By specifying the seed value, the same random data will be generated again.
*
*
* Example:
*
{@code
* // Generates a different UUID each time
* UUID result = Instancio.create(UUID.class);
*
* // Generates the same UUID each time
* UUID result = Instancio.of(UUID.class)
* .withSeed(1234)
* .create();
* }
*
* @param seed for the random number generator
* @return API builder reference
* @since 4.0.0
*/
InstancioOperations withSeed(long seed);
}