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

it.auties.whatsapp.model.request.Node Maven / Gradle / Ivy

There is a newer version: 3.5.1
Show newest version
package it.auties.whatsapp.model.request;

import it.auties.whatsapp.util.Json;
import lombok.NonNull;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * An immutable model class that represents the primary unit used by WhatsappWeb's WebSocket to
 * communicate with the client
 *
 * @param description a non-null String that describes the content of this node
 * @param attributes  a non-null Map that describes the metadata of this object
 * @param content     a nullable object: a List of {@link Node}, a {@link String} or a
 *                    {@link Number}
 */
public record Node(@NonNull String description, @NonNull Attributes attributes,
                   Object content) {
    /**
     * Constructs a Node that only provides a non-null tag
     *
     * @param description a non-null String that describes the data that this object holds
     * @return a new node with the above characteristics
     */
    public static Node of(@NonNull String description) {
        return new Node(description, Attributes.of(), null);
    }

    /**
     * Constructs a Node that provides a non-null tag and a nullable content
     *
     * @param description a non-null String that describes the data that this object holds
     * @param content     a nullable object, usually a List of {@link Node}, a {@link String} or a
     *                    {@link Number}
     * @return a new node with the above characteristics
     */
    public static Node of(@NonNull String description, Object content) {
        return new Node(description, Attributes.of(), content);
    }

    /**
     * Constructs a Node that provides a non-null tag, a non-null map of attributes and a nullable
     * content
     *
     * @param description a non-null String that describes the data that this object holds
     * @param attributes  a non-null Map that describes the metadata of this object
     * @param content     a nullable object, usually a List of {@link Node}, a {@link String} or a
     *                    {@link Number}
     * @return a new node with the above characteristics
     */
    public static Node of(@NonNull String description, @NonNull Map attributes, Object content) {
        return new Node(description, Attributes.ofNullable(attributes), content);
    }

    /**
     * Constructs a Node that provides a non-null tag and a non-null map of attributes
     *
     * @param description a non-null String that describes the data that this object holds
     * @param attributes  a non-null Map that describes the metadata of this object
     * @return a new node with the above characteristics
     */
    public static Node ofAttributes(@NonNull String description, @NonNull Map attributes) {
        return new Node(description, Attributes.ofNullable(attributes), null);
    }


    /**
     * Constructs a Node that provides a non-null tag and a nullable var-args of children
     *
     * @param description a non-null String that describes the data that this object holds
     * @param children    the nullable children of this node
     * @return a new node with the above characteristics
     */
    public static Node ofChildren(@NonNull String description, Node... children) {
        return ofChildren(description, Arrays.asList(children));
    }

    /**
     * Constructs a Node that provides a non-null tag and a nullable var-args of children
     *
     * @param description a non-null String that describes the data that this object holds
     * @param children    the nullable children of this node
     * @return a new node with the above characteristics
     */
    public static Node ofChildren(@NonNull String description, Collection children) {
        return new Node(description, Attributes.of(), requireNonNullNodes(children));
    }

    private static List requireNonNullNodes(Collection nodes) {
        if (nodes == null) {
            return null;
        }
        var results = nodes.stream().filter(Objects::nonNull).toList();
        return results.isEmpty() ? null : results;
    }

    /**
     * Constructs a Node that provides a non-null tag, a non-null map of attributes and a nullable
     * var-args of children
     *
     * @param description a non-null String that describes the data that this object holds
     * @param attributes  a non-null Map that describes the metadata of this object
     * @param children    the nullable children of this node
     * @return a new node with the above characteristics
     */
    public static Node ofChildren(@NonNull String description, @NonNull Map attributes, Node... children) {
        return ofChildren(description, attributes, Arrays.asList(children));
    }

    /**
     * Constructs a Node that provides a non-null tag, a non-null map of attributes and a nullable
     * var-args of children
     *
     * @param description a non-null String that describes the data that this object holds
     * @param attributes  a non-null Map that describes the metadata of this object
     * @param children    the nullable children of this node
     * @return a new node with the above characteristics
     */
    public static Node ofChildren(@NonNull String description, @NonNull Map attributes, Collection children) {
        return new Node(description, Attributes.ofNullable(attributes), requireNonNullNodes(children));
    }

    /**
     * Returns the content of this object as string
     *
     * @return an optional
     */
    public Optional contentAsString() {
        if (content instanceof String string) {
            return Optional.of(string);
        } else if (content instanceof byte[] bytes) {
            return Optional.of(new String(bytes, StandardCharsets.UTF_8));
        } else {
            return Optional.empty();
        }
    }

    /**
     * Returns the content of this object as bytes
     *
     * @return an optional
     */
    public Optional contentAsBytes() {
        return content instanceof byte[] bytes ? Optional.of(bytes) : Optional.empty();
    }

    /**
     * Returns the content of this object as a long
     *
     * @return an optional
     */
    @SuppressWarnings("unused")
    public OptionalLong contentAsLong() {
        return content instanceof Number number ? OptionalLong.of(number.longValue()) : OptionalLong.empty();
    }

    /**
     * Returns the content of this object as a double
     *
     * @return an optional
     */
    @SuppressWarnings("unused")
    public OptionalDouble contentAsDouble() {
        return content instanceof Number number ? OptionalDouble.of(number.doubleValue()) : OptionalDouble.empty();
    }

    /**
     * Returns the content of this object as a double
     *
     * @return an optional
     */
    @SuppressWarnings("unused")
    public Optional contentAsBoolean() {
        if (content instanceof String string) {
            return Optional.of(Boolean.parseBoolean(string.toLowerCase(Locale.ROOT)));
        } else if (content instanceof byte[] bytes) {
            return Optional.of(Boolean.parseBoolean(new String(bytes, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT)));
        } else {
            return Optional.empty();
        }
    }

    /**
     * Checks whether the child node with the given description exists
     *
     * @return true if a child node with the given description exists
     */
    public boolean hasNode(String description) {
        return children().stream().anyMatch(node -> Objects.equals(node.description(), description));
    }

    /**
     * Returns a non-null list of children of this node
     *
     * @return a non-null list
     */
    public LinkedList children() {
        if (content == null) {
            return new LinkedList<>();
        }
        if (!(content instanceof Collection collection)) {
            return new LinkedList<>();
        }
        return collection.stream()
                .filter(entry -> entry instanceof Node)
                .map(entry -> (Node) entry)
                .collect(Collectors.toCollection(LinkedList::new));
    }

    /**
     * Checks whether this node's description is equal to the one provided
     *
     * @param description the non-null description to check against
     * @return a boolean
     */
    public boolean hasDescription(@NonNull String description) {
        return Objects.equals(description(), description);
    }

    /**
     * Finds the first child node
     *
     * @return an optional
     */
    public Optional findNode() {
        return children().stream().findFirst();
    }

    /**
     * Returns the first node that matches the description provided
     *
     * @return an optional
     */
    public Optional findNode(String description) {
        return children().stream().filter(node -> Objects.equals(node.description(), description)).findFirst();
    }

    /**
     * Returns all the nodes that match the description provided
     *
     * @return an optional body, present if a result was found
     */
    public List findNodes(String description) {
        return children().stream().filter(node -> Objects.equals(node.description(), description)).toList();
    }

    /**
     * Returns the size of this object
     *
     * @return an unsigned int
     */
    public int size() {
        var descriptionSize = 1;
        var attributesSize = 2 * attributes.toMap().size();
        var contentSize = hasContent() ? 1 : 0;
        return descriptionSize + attributesSize + contentSize;
    }

    /**
     * Returns whether this object's content is non-null
     *
     * @return true if this object has a content
     */
    public boolean hasContent() {
        return Objects.nonNull(content);
    }

    /**
     * Constructs a new request from this node
     *
     * @return a non null request
     * @throws NullPointerException if no valid jid can be found
     */
    public Request toRequest(Function filter) {
        if (id() == null) {
            attributes.put("id", UUID.randomUUID().toString());
        }
        return Request.of(this, filter);
    }

    /**
     * Returns the nullable id of this node
     *
     * @return a nullable String
     */
    public String id() {
        return attributes.getString("id", null);
    }

    /**
     * Checks if this object is equal to another
     *
     * @param other the reference object with which to compare
     * @return whether {@code other} is equal to this object
     */
    @Override
    public boolean equals(Object other) {
        return other instanceof Node that && Objects.equals(this.description(), that.description()) && Objects.equals(this.attributes(), that.attributes()) && (Objects.equals(this.content(), that.content()) || this.content() instanceof byte[] theseBytes && that.content() instanceof byte[] thoseBytes && Arrays.equals(theseBytes, thoseBytes));
    }

    /**
     * Converts this node into a String
     *
     * @return a non null String
     */
    @Override
    public String toString() {
        var description = this.description.isBlank() || this.description.isEmpty() ? "" : "description=%s".formatted(this.description);
        var attributes = this.attributes.toMap().isEmpty() ? "" : ", attributes=%s".formatted(this.attributes.toMap());
        var content = this.content == null ? "" : ", content=%s".formatted(this.content instanceof byte[] bytes ? Arrays.toString(bytes) : this.content);
        return "Node[%s%s%s]".formatted(description, attributes, content);
    }

    /**
     * Converts this node into a JSON String
     *
     * @return a non null String
     */
    public String toJson() {
        return Json.writeValueAsString(this, true);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy