HappyTree API

A Java API designed for handling objects with tree-like behavior

Transform linear Java objects into hierarchical tree structures. Add, remove, cut, copy, and export to JSON/XML with ease.

What is HappyTree?

🌳

Tree Structure API

HappyTree is a data structure API designed for handling Java objects that have tree-like behavior, where an @Id attribute is referenced as a @Parent attribute of its children.

πŸ”„

Transformation Process

Transform collections of plain Java objects into actual hierarchical tree structures in memory, where each object is wrapped into a tree node called Element.

⚑

Powerful Operations

Perform operations like adding/removing children, moving nodes, copying to other trees, and converting structures to JSON/XML formats.

Key Features

πŸ“¦

Object Management

Handle Java objects as tree nodes with operations like copying, cutting, removing, creating, and updating.

πŸ”—

Linear to Tree

Transform linear data structures with tree-like relationships into actual hierarchical trees.

✨

Create from Scratch

Build new trees from scratch, adding elements one by one to create custom structures.

πŸ“

JSON/XML Export

Convert your tree structures to JSON or XML format for easy serialization and data exchange.

🎯

Type Safety

Generic-based API with full type safety and compile-time checking for your domain objects.

πŸ› οΈ

Easy Integration

Simple annotation-based configuration with Maven/Gradle support and minimal setup required.

Perfect For

πŸ“ Directory Structures
🏒 Organizational Charts
πŸ–₯️ UI Component Trees
πŸ›οΈ Product Categories
πŸ’¬ Comment/Reply Systems
πŸ—ΊοΈ Menu Navigation
🏷️ Taxonomy
πŸ“ Location Hierarchy

How to Use

1

Annotate Your Class

Add the required annotations to your Java class:

@Tree
public class Directory {
    @Id
    private Integer directoryId;
    
    @Parent
    private Integer directoryParentId;
    
    private String directoryName;
    
    // Default constructor and Getters & Setters
}
2

Initialize a Session

Create a tree from an existing collection:

// From existing collection
Collection<Directory> directories = getDirectories();

TreeManager manager = HappyTree.createTreeManager();
TreeTransaction transaction = manager.getTransaction();
transaction.initializeSession("myTree", directories);

// Or create an empty tree
transaction.initializeSession("emptyTree", Directory.class);
3

Handle Your Tree

Perform operations on your tree elements:

// Get elements
Element<Directory> admin = manager.getElementById(adminId);
Element<Directory> accessControl = manager.getElementById(accessId);

// Move elements
manager.cut(accessControl, admin);

// Create new elements
Directory security = new Directory();
security.setDirectoryId(securityId);
security.setDirectoryParentId(adminId);

Element<Directory> securityElement = 
    manager.createElement(securityId, adminId, security);
securityElement = manager.persistElement(securityElement);

// Update the element
security = securityElement.unwrap();
security.setDirectoryName("Security");
securityElement.wrap(security);
securityElement = manager.updateElement(securityElement);

// Export the element 'Security' to JSON
System.out.println(securityElement.toJSON());

// Export the entire tree to XML
Element<Directory> root = manager.root();
System.out.println(root.toXML());

Installation

Maven

Add to your pom.xml:

<dependency>
    <groupId>com.madzera</groupId>
    <artifactId>happytree</artifactId>
    <version>2.0.0</version>
</dependency>

Gradle

Add to your build.gradle:

implementation 'com.madzera:happytree:2.0.0'
⚠️ Note for v1.0.0 users:

The groupId has changed from com.madzera.happytree to com.madzera in version 2.0.0

Documentation & Resources

Quick Start

// 1. Define your model
@Tree
public class Directory {
    @Id
    private Integer directoryId;
    @Parent
    private Integer directoryParentId;
	
    private String directoryName;
    
    // Constructors, getters, setters...
}

// 2. Create or retrieve data from a previous structure (DB)
List<Directory> directories = Arrays.asList(
    new Directory(1, null, "Root"),
    new Directory(2, 1, "Documents"),
    new Directory(3, 1, "Pictures"),
    new Directory(4, 2, "Work"),
    new Directory(5, 2, "Personal")
);

// 3. Initialize the tree
TreeManager manager = HappyTree.createTreeManager();
TreeTransaction transaction = manager.getTransaction();
transaction.initializeSession("fileSystem", directories);

// 4. Retrieve the specific 'Pictures' and 'Personal' nodes
Element<Directory> pictures = manager.getElementById(3);
Element<Directory> personal = manager.getElementById(5);

// 5. Move 'Personal' from 'Documents' to 'Pictures'
Element<Directory> personal = manager.getElementById(5);
manager.cut(personal, pictures);

// 6. Export the result in JSON format and print it
Element<Directory> root = manager.root();
String json = root.toJSON();
System.out.println(json);

Element

Represents a tree node - access data, navigate children, and export to JSON/XML

Method Description
getId() Obtains the element identifier. This identifier is unique within the tree session when attached to the tree
setId(Object id) Sets the element identifier. The change requires updating the element to take effect. The identifier must be unique and nonnull
getParent() Obtains the parent identifier of this element
setParent(Object parent) Sets the parent identifier reference of this element. If null or nonexistent parent, the element will be at root level when persisted/updated
getChildren() Obtains all child elements of the current element. This includes all descendants recursively
addChildren(Element<T> child) Adds a new child element into the current element. If the child contains children, they will also be added
addChildren(Collection<Element<T>> children) Adds a list of child elements to be contained in the current children list. Includes all nested children recursively
getElementById(Object id) Searches within the current element for an element according to the @Id parameter. Returns null if not found. Search is performed recursively
removeChildren(Collection<Element<T>> children) Removes a subset of elements within this one. All children and elements below the hierarchy are also removed recursively
removeChild(Element<T> child) Removes the specified child element from the children list. All its children and elements below are also removed recursively
removeChild(Object id) Removes the element from the children list by @Id. The element and all its children are removed
wrap(T object) Encapsulates any object node within the element, as long as it has the same class type as other objects in the same tree session
unwrap() Returns a copy of the object node wrapped in this element. Provides access to the encapsulated object
attachedTo() Returns the TreeSession instance to which this element belongs. An element is always associated with a session
lifecycle() Returns the current lifecycle state of this element (NOT_EXISTED, ATTACHED, or DETACHED)
toJSON() Converts the whole element structure into a JSON format (minified). This includes all children recursively
toPrettyJSON() Converts the whole element structure into a well-formatted JSON string. This includes all children recursively
toXML() Converts the whole element structure into an XML string (minified). This includes all children recursively
toPrettyXML() Converts the whole element structure into a well-formatted XML string. This includes all children recursively
search(Predicate<Element<T>> condition) Searches for elements that satisfy a specific condition within this element and its children recursively. Returns a list of matching elements with their hierarchical structure preserved
apply(Consumer<Element<T>> action) Applies a function to be performed on this element and all its children recursively. Changes are not automatically reflected and require persist/update operations
apply(Consumer<Element<T>> action, Predicate<Element<T>> condition) Applies a function to be performed on elements that satisfy the specified condition within the element subtree. Changes require persist/update operations

TreeManager

Handles elements within tree sessions - create, update, move, copy, and query tree nodes

Method Description
cut(Element<T> from, Element<T> to) Cuts the source element to inside of the target element, whether for the same session or not (they must have the same object type). All children of the source element will be cut as well. If target is null, moves to root level
cut(Object from, Object to) Cuts an element identified by its @Id and moves it inside another element within the same session. Returns null if the target element is not found. If the target element is null or not found, the source element is moved to the root level
copy(Element<T> from, Element<T> to) Copies the source element into the target element in another tree session. The entire structure of the copied element will be pasted inside the target element. Copying within the same tree session would result in a duplicate @Id exception
removeElement(Element<T> element) Removes the corresponding element from the tree session and returns the removed element. After removal, the element and all its children will have the NOT_EXISTED state
removeElement(Object id) Removes the element by its @Id. All children of the found element are removed as well from the removed element itself. Returns null if @Id cannot be found
getElementById(Object id) Returns the element given its @Id in the tree session. Returns null if the @Id is null or if the element cannot be found in the tree
containsElement(Element<T> parent, Element<T> descendant) Verifies whether the parent element is inside of it the descendant element in the current session. Returns false if elements are null or not attached
containsElement(Object parent, Object descendant) Verifies whether the parent element (identified by @Id) contains the descendant element (identified by @Id) within the current session. Returns false if either element is not found
containsElement(Element<T> element) Verifies that the current tree session has the specified element. Returns false if element is null, not found, or not in ATTACHED state
containsElement(Object id) Verifies that the current tree session has the specified element by the given @Id. Returns false if element is not found or @Id is null
createElement(Object id, Object parent, T wrappedNode) Creates an element with the @Id, parent and the wrapped object node. Returns a new element with the NOT_EXISTED state in lifecycle. Must be persisted to be inserted to the tree
persistElement(Element<T> newElement) Persists a new element into the current tree session. The new element must have a unique identifier and NOT_EXISTED state. Returns a copy with ATTACHED state
updateElement(Element<T> element) Updates the state of the element within the tree. Returns a copy with ATTACHED state
getTransaction() Obtains the TreeTransaction instance associated with this manager. Every operation defined in this interface needs to check the transaction
root() Returns the root of the tree in the current session. The root encompasses all other elements and has no @Id, @Parent, or object wrapped node
search(Predicate<Element<T>> condition) Searches for elements that satisfy a specific condition within the entire tree structure. Returns a list of elements (without their children) that match the condition
apply(Consumer<Element<T>> action) Applies a function to be performed on all elements within the entire tree structure (except root). Changes are automatically reflected on the tree session
apply(Consumer<Element<T>> action, Predicate<Element<T>> condition) Applies a function to be performed on elements that satisfy the specified condition within the entire tree structure (except root). Changes are automatically reflected on the tree session

TreeSession

Represents a tree instance - provides session identification and state

Method Description
getSessionId() Returns the session identifier name. A session identifier is defined when the session is initialized and must be unique
isActive() Verifies whether the session is active. Returns true if the session is active (can be handled), false if deactivated (exists in memory but cannot be handled)
tree() Returns the entire tree session structure, represented by the root element. From the root element, it is possible to navigate through all children recursively, accessing the entire tree structure

TreeTransaction

Manages tree sessions - initialize, activate, clone, and destroy sessions

Method Description
initializeSession(String identifier, Class<T> type) Initializes a new empty tree session with the specified identifier. Creates an empty tree where the API client must create elements one by one. The session is automatically checked out as the current session
initializeSession(String identifier, Collection<T> nodes) Initializes a session with a specified identifier and transforms a list of linear objects (with logical tree structure) into an actual tree structure through the API Transformation Process. The session is automatically available as the current session
destroySession(String identifier) Removes the session with the specified identifier permanently. The tree and its elements within this session are also removed and cannot be retrieved
destroySession() Removes the current session permanently. The tree and its elements within this session are also removed. The API client needs to specify a new session to be checked out after removal
destroyAllSessions() Removes all registered sessions permanently. The removal occurs for both activated and deactivated sessions
sessionCheckout(String identifier) Selects a tree session to work with. The current session remains in the background while the checked-out session becomes the current session. Passing null for non-existent identifier cancels the current session
activateSession(String identifier) Activates a session by the specified identifier. With an active session, its elements can be handled freely within the tree. It does not automatically make it as the current session
activateSession() Activates the current session. With an active session, its elements can be handled freely within the tree. The session will always be active after invoking this method
deactivateSession(String identifier) Deactivates a session by the specified identifier. The session is just disabled but not removed. With a deactivated session, its elements cannot be handled
deactivateSession() Deactivates the current session. The session is just disabled but not removed from the list of registered sessions. With a deactivated session, its elements cannot be handled
sessions() Returns the list of all registered sessions. The list includes both activated and deactivated sessions
cloneSession(String from, String to) Replicates the tree session defined by the "from" identifier to the session defined by the "to" identifier. Faithfully reproduces all elements from source tree to target tree. If target exists, it is replaced. It does not automatically check out the cloned session after the cloning process ends
cloneSession(TreeSession from, String to) Replicates the tree session defined by the "from" session instance to the session defined by the "to" identifier. Faithfully reproduces all elements from source tree to target tree. If target exists, it is replaced. It does not automatically check out the cloned session after the cloning process ends
currentSession() Returns the current session of the transaction. The current session is the one that the transaction is referring to at this moment. Returns null if no session is checked out

Practical Usage Examples

1. Moving Elements Between Parents

Reorganize your tree structure by moving elements to different parents.

// Initialize tree with directories
TreeManager manager = HappyTree.createTreeManager();
TreeTransaction transaction = manager.getTransaction();
transaction.initializeSession("fileSystem", directories);

// Get elements
Element<Directory> personal = manager.getElementById(5);
Element<Directory> pictures = manager.getElementById(3);

// Move 'Personal' folder under 'Pictures'
manager.cut(personal, pictures);

// Verify the move
assertTrue(manager.containsElement(pictures, personal));
System.out.println("Folder moved successfully!");

2. Creating and Persisting New Elements

Build trees dynamically by creating new elements from scratch.

// Create empty tree session
transaction.initializeSession("newTree", Directory.class);

// Create new directory object
Directory security = new Directory();
security.setDirectoryId(100);
security.setDirectoryParentId(null);
security.setDirectoryName("Security");

// Wrap in Element and persist
Element<Directory> securityElement = 
    manager.createElement(100, null, security);
securityElement = manager.persistElement(securityElement);

// Element is now part of the tree
assertEquals("ATTACHED", securityElement.lifecycle());
System.out.println("New element created: " + security.getDirectoryName());

3. Searching Elements with Conditions

Find specific elements using predicates and functional programming.

// Search for directories containing "Doc"
List<Element<Directory>> results = manager.search(element -> {
    Directory dir = element.unwrap();
    return dir.getDirectoryName().contains("Doc");
});

// Process results
results.forEach(element -> {
    Directory dir = element.unwrap();
    System.out.println("Found: " + dir.getDirectoryName());
});

// Search within a specific element
Element<Directory> root = manager.root();
List<Element<Directory>> children = root.search(e -> 
    e.getChildren().size() > 2
);

4. Bulk Operations with Apply

Apply transformations to multiple elements at once.

// Apply operation to all elements in tree
manager.apply(element -> {
    Directory dir = element.unwrap();
    // Add prefix to all directory names
    dir.setDirectoryName("[ARCHIVED] " + dir.getDirectoryName());
    element.wrap(dir);
});

// Apply only to elements matching condition
manager.apply(
    element -> {
        Directory dir = element.unwrap();
        dir.setDirectoryName(dir.getDirectoryName().toUpperCase());
        element.wrap(dir);
    },
    element -> element.getChildren().size() == 0 // Only leaf nodes
);

// Changes are automatically reflected in the tree
System.out.println("Bulk update completed!");

5. Working with Multiple Sessions

Manage multiple independent trees simultaneously.

TreeManager manager = HappyTree.createTreeManager();
TreeTransaction transaction = manager.getTransaction();

// Initialize multiple sessions
transaction.initializeSession("personal", personalDirs);
transaction.initializeSession("work", workDirs);

// Switch between sessions
transaction.sessionCheckout("personal");
Element<Directory> personalRoot = manager.root();

transaction.sessionCheckout("work");
Element<Directory> workRoot = manager.root();

// Copy element between sessions
Element<Directory> project = manager.getElementById(42);
manager.copy(project, personalRoot);

// Clone entire session
transaction.cloneSession("work", "work-backup");
System.out.println("Session cloned successfully!");

6. Exporting Tree Structures

Convert your tree to JSON or XML format for serialization.

// Get root element
Element<Directory> root = manager.root();

// Export to minified JSON
String json = root.toJSON();
System.out.println(json);

// Export to formatted JSON (readable)
String prettyJson = root.toPrettyJSON();
System.out.println(prettyJson);

// Export to XML
String xml = root.toXML();
System.out.println(xml);

// Export formatted XML
String prettyXml = root.toPrettyXML();

// Export specific subtree
Element<Directory> documents = manager.getElementById(2);
String subtreeJson = documents.toPrettyJSON();

// Save to file
Files.write(Paths.get("tree-export.json"), 
    prettyJson.getBytes(StandardCharsets.UTF_8));
2.0.0
Latest Version
Java 8+
Compatibility
MIT
License
A+
Code Quality

Contributing

We welcome contributions! Whether it's bug reports, feature requests, documentation improvements, or code contributions, your help is appreciated.