# pixie **Repository Path**: mirrors_tomitribe/pixie ## Basic Information - **Project Name**: pixie - **Description**: Tiny but powerful library creating your java objects with a bit of magical pixie dust - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-21 - **Last Updated**: 2026-02-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README = Pixie Pixie is a tiny 100k jar that handles configuration, dependency injection and events. It can handle any scenario where you would use reflection to instantiate a Java object. .Maven dependency: [source,xml] ---- org.tomitribe.pixie pixie 2.0 ---- == Example Let's imagine we want to use Pixie to configure and build two classes we've created called `Messages` and `Address`. Our first step is to annotate the constructor parameters with Pixie annotations. Pixie will map the configuration to our constructor based on the names we use. [source,java] ---- import org.tomitribe.pixie.Component; import org.tomitribe.pixie.Default; import org.tomitribe.pixie.Name; import org.tomitribe.pixie.Param; public class Person { private final String name; private final Integer age; private final Address address; public Person(@Name final String name, @Param("age") final Integer age, @Paran("address") @Component final Address address) { this.name = name; this.age = age; this.address = address; } //... } public class Address { private final String street; private final String city; private final State state; private final int zipcode; private final String country; public Address(@Param("street") final String street, @Param("city") final String city, @Param("state") final State state, @Param("zipcode") final int zipcode, @Param("country") @Default("USA") final String country) { this.street = street; this.city = city; this.state = state; this.zipcode = zipcode; this.country = country; } //... } public enum State { WI, MN, CA; } ---- We can use the Pixie `System` to configure and build the above objects via either a Properties file defintion or in code via a Builder API === Properties Our properties file might look like so. The file name and location can be anything as it's our job to read this into a `java.util.Properties` instance. [source,properties] ---- jon = new://org.example.Person jon.age = 46 jon.address = @home home = new://org.example.Address home.street = 823 Roosevelt Street home.city = River Falls home.state = WI home.zipcode = 54022 home.country = USA ---- The following code would build a Pixie `System` via a `java.util.Properties` instance and lookup the `Messages` instance by type. [source,java] ---- import java.util.Properties; import org.example.Person; import org.tomitribe.pixie.System; //... public static void main(String[]) { final Properties properties = new Properties(); properties.load(...); // read the properties file final System system = new System(properties); final Person person = system.get(Person.class); assertEquals("jane", person.getName(); final Address address = person.getAddress(); assertNotNull(address); assertEquals("820 Roosevelt Street", address.getStreet()); } ---- === Builder API Alternatively, we can skip the properties and build our `System` in code fluently. [source,java] ---- import java.util.Properties; import org.example.Person; import org.tomitribe.pixie.System; //... public static void main(String[]) { final System system = System.builder() .definition(Person.class, "jane") .param("age", 37) .comp("address", "home") .definition(Address.class, "home") .param("street", "820 Roosevelt Street") .param("city", "River Falls") .param("state", "WI") .param("zipcode", "54022") .build(); final Person person = system.get(Person.class); //... } ---- == Pixie Constructor Annotations Pixie supports constructor injection. Each parameter the constructor Pixie will use must be annotated with either `@Param`, `@Component`, `@Event` or `@Name`. [options="header"] |=== | Annotation | Purpose | Example Usage | `@Param` | Maps a constructor parameter to a config property | `@Param("username") final String username` | `@Default` | Provides a default value if the property is missing | `@Param("country") @Default("USA") final String country` | `@Component` | Injects a dependent object built by or given to Pixie `System` | `@Component final PaymentProcessor paymentProcessor` | `@Nullable` | Allows a property to be `null` if missing | `@Nullable @Param("footer") final String footer` | `@Name` | Injects the component's name from the config | `@Name final String serviceName` | `@Event` | Injects a `Consumer` to fire events | `@Event final Consumer event` | `@Observes` | Marks a method as an event listener | `public void onEvent(@Observes OrderPlaced event)` |=== All the above annotations are in the `org.tomitribe.pixie` package. == `@Param` *Purpose:* Binds a constructor parameter to a configuration property. *Usage:* Pixie will automatically inject values from a properties file or the builder API. .Example: [source,java] ---- public final class User { private final String username; private final int age; public User(@Param("username") final String username, @Param("age") final int age) { this.username = username; this.age = age; } } ---- *Maps to a properties file entry:* [source,properties] ---- user=new://org.example.User user.username=alice user.age=30 ---- Any Java type that can be created from a `String` is supported. Pixie will inspect the java class and look for one of the following: - Public constructor with a single parameter of type `String` - Public static method with a single parameter of type `String` returning an instance of the type --- == `@Default` *Purpose:* Specifies a default value for a constructor parameter if it is not set in the configuration. .Example: [source,java] ---- public final class Address { private final String country; public Address(@Param("country") @Default("USA") final String country) { this.country = country; } } ---- If `country` is missing from the config, `"USA"` is used. Applies to both `@Param` and `@Component`. When used on `@Component` it implies the name of the component that should be injected. --- == `@Component` *Purpose:* Indicates that a constructor parameter should be injected as a component dependency. .Example: [source,java] ---- public final class ShoppingCart { private final PaymentProcessor paymentProcessor; public ShoppingCart(@Param("processor") @Component final PaymentProcessor paymentProcessor) { this.paymentProcessor = paymentProcessor; } } ---- Pixie can resolve this reference by name or by type. .Resolution by name: [source,properties] ---- cart=new://org.example.ShoppingCart cart.processor=@stripe ---- With the above configuration Pixie will look in the `System` for an object with the name `stripe` and inject it as the value of the `processor` when constructing the `ShoppingCart`. A `ConstructionFailedException` will be thrown if any of the following scenarios occurs: - no `@Param` was provided - no object with that name is found - if the object found is of the wrong type. .Resolution by type: [source,properties] ---- cart=new://org.example.ShoppingCart ---- In the above configuration the `processor` name has not be specified. In this situation, Pixie will look in the `System` for any object with the the type `PaymentProcessor` and inject it as the value of the `processor` when constructing the `ShoppingCart`. If there are *multiple instances* of `PaymentProcessor` they will be sorted in descending order by name and the first will be picked. A `ConstructionFailedException` will be thrown if no objects with that type are found. === Adding Custom Components The `@Component` annotation can be used to resolve components which are added directly to the Pixie `System`. .Properties: [source,properties] ---- jane=new://org.example.Person\n" + jane.age = 37 jane.address=@home ---- In the above properties, the Person object has a `@Component` reference to `Address` called `home` which is not defined. The `home` instance can be added directly to the Pixie `System` before we load the properties. .Adding to Pixie System: [source,java] ---- final Properties properties = //... final System system = new System(); system.add("home", new Address("820 Roosevelt Street","River Falls", State.WI, 54022, "USA")); system.load(properties); final Person person = system.get(Person.class); assertNotNull(person.getAddress()); ---- In the above code we've directly created the `Address` instance and handed it to Pixie `System` with the name `home`. --- == `@Nullable` *Purpose:* Marks a constructor parameter as optional (can be `null` if not configured). .Example: [source,java] ---- public final class Notification { private final String message; private final String footer; public Notification(@Param("message") final String message, @Nullable @Param("footer") final String footer) { this.message = message; this.footer = footer; } } ---- If `footer` is missing from the config, it will be `null` instead of throwing an error. --- == `@Name` *Purpose:* Injects the component’s name from the configuration. .Example: [source,java] ---- public final class Service { private final String serviceName; public Service(@Name final String serviceName) { this.serviceName = serviceName; } } ---- If configured as `myService = new://com.foo.Service`, the constructor will receive `"myService"`. --- == `@Event` *Purpose:* Injects an event consumer (`Consumer`) into a component so it can fire events. .Example: [source,java] ---- public final class OrderService { private final Consumer orderPlacedEvent; public OrderService(@Event final Consumer orderPlacedEvent) { this.orderPlacedEvent = orderPlacedEvent; } public void placeOrder(final String orderId) { orderPlacedEvent.accept(new OrderPlaced(orderId)); } } ---- Pixie will inject a `Consumer` that calls `System.fire(event)` which will invoke all observer methods in all components in the `System`. --- == `@Observes` *Purpose:* Marks a method as an event listener. .Example: [source,java] ---- public final class OrderListener { public void onOrderPlaced(@Observes final OrderPlaced event) { System.out.println("Order placed: " + event.getOrderId()); } } ---- When `OrderPlaced` is fired, this method will be called automatically. It is possible to listen for events by any assignable type, even `java.lang.Object` .Example: [source,java] ---- public final class EverythingListener { public void onEvent(@Observes final Object event) { System.out.println("Event observed: " + event); } } ---- --- == Configuration Validation Pixie provides strict validation to ensure configuration correctness and prevent common issues with properties files. === Case Insensitivity All properties in Pixie are **case insensitive**, meaning users will not encounter failures due to incorrect capitalization. For example, the following entries are treated as equivalent: [source,properties] ---- user.name=Alice User.Name=Alice USER.NAME=Alice ---- Regardless of how the property is written, it will be correctly matched and retrieved. === Strict Property Validation Pixie enforces strict validation of configuration properties to prevent misconfigurations: - If a property is **specified in the configuration file but does not exist in the corresponding class**, Pixie will **throw an exception** at startup. - This ensures that typos or removed properties do not lead to silent failures. For example, given the following properties file: [source,properties] ---- app.mode=production app.timeout=5000 ---- If the `app.timeout` property is removed from the Java class but remains in the configuration file, Pixie will **fail fast** with an error, preventing users from relying on "dead" properties.