All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.instancio.InstancioApi Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
/*
 * Copyright 2022-2023 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.documentation.ExperimentalApi;
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.Settings;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Instancio API for generating instances of a class populated with random data.
 *
 * @param  type to create
 * @since 1.0.1
 */
public interface InstancioApi {

    /**
     * Creates a new instance of a class and populates it with data.
     * 

* Example: *

{@code
     * Person person = Instancio.of(Person.class).create();
     * }
*

* The returned object will have all its fields populated with random data, * including collection and array fields. * * @return a fully populated object * @since 1.0.1 */ T create(); /** * Returns a {@link Result} containing the created object and seed value * used to generate its values. The seed value can be used to reproduce * the same object again. * * @return result containing the created object * @since 1.5.1 */ Result asResult(); /** * Creates an infinite stream of distinct, fully populated objects. *

* Example: *

{@code
     * List persons = Instancio.of(Person.class)
     *     .stream()
     *     .limit(5)
     *     .collect(Collectors.toList());
     * }
* * @return an infinite stream of distinct, populated objects * @since 1.1.9 */ Stream stream(); /** * Creates a model containing all the information for populating a class. *

* The model can be useful when class population needs to be customised * and the customisations need to be re-used in different parts of the code. * *

Example: *

{@code
     * Model simpsons = Instancio.of(Person.class)
     *     .set(field(Person::getLastName), "Simpson")
     *     .set(field(Address::getCity), "Springfield")
     *     .generate(field(Person::getAge), gen -> gen.ints().range(40, 50))
     *     .toModel();
     *
     * Person homer = Instancio.of(simpsons)
     *     .set(field(Person::getFirstName), "Homer")
     *     .set(all(Gender.class), Gender.MALE)
     *     .create();
     *
     * Person marge = Instancio.of(simpsons)
     *     .set(field(Person::getFirstName), "Marge")
     *     .set(all(Gender.class), Gender.FEMALE)
     *     .create();
     * }
* * @return a model that can be used as a template for creating objects * @since 1.0.1 */ Model toModel(); /** * 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 */ InstancioApi 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 */ InstancioApi 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) */ InstancioApi 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) */ InstancioApi 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 */ InstancioApi 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 */ InstancioApi 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 2.6.0 */ @ExperimentalApi InstancioApi 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();
     * }
* * @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 1.0.4 */ InstancioApi onComplete(TargetSelector selector, OnCompleteCallback callback); /** * Maps target field or class to the given subtype. This can be used * in the following cases: * *
    *
  1. to specify an implementation for interfaces or abstract classes
  2. *
  3. to override default implementations used by Instancio
  4. *
* 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 1.4.0 */ InstancioApi 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[] assignments = {
     *     Assign.given(Order::getStatus)
     *             .is(OrderStatus.SHIPPED)
     *             .supply(field(Order::getDeliveryDueDate), () -> LocalDate.now().plusDays(2)),
     *
     *     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(assignments)
     *     .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 3.0.0 */ @ExperimentalApi InstancioApi 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 2.9.0 */ InstancioApi withMaxDepth(int maxDepth); /** * 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 * @since 1.0.1 */ InstancioApi 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 1.0.1 */ InstancioApi withSeed(long seed); /** * Disables strict mode in which unused selectors trigger an error. * In lenient mode unused selectors are simply ignored. *

* This method is a shorthand for: * *

{@code
     * Example example = Instancio.of(Example.class)
     *     .withSettings(Settings.create().set(Keys.MODE, Mode.LENIENT))
     *     .create();
     * }
* * @return API builder reference * @since 1.4.1 */ InstancioApi lenient(); /** * Outputs debug information to {@code System.out}. This includes: * *
    *
  • current {@link Settings}
  • *
  • node hierarchy, including the type and depth of each node
  • *
  • seed used to create the object
  • *
* *

Note: {@code verbose()} information is only output when creating * an object. This method has no effect when creating a {@link Model}. * *

Warning: this method has a significant performance impact. * It is recommended to remove the call to {@code verbose()} after * troubleshooting is complete. * * @return API builder reference * @since 3.0.0 */ @ExperimentalApi InstancioApi verbose(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy