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

xjs.core.JsonReference Maven / Gradle / Ivy

There is a newer version: 0.36
Show newest version
package xjs.core;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.Function;

/**
 * The JSON reference is an accessor to a {@link JsonValue} which can be made
 * available to multiple containers. It is primarily intended to facilitate
 * JEL expressions, but may also be useful for general-purpose data transforms.
 *
 * 

For example, to increment each number in a {@link JsonArray JSON array}: * *

{@code
 *   array.references()
 *     .stream()
 *     .filter(ref ->
 *       ref.visit().isNumber())
 *     .forEach(ref ->
 *       ref.apply(n -> n.asDouble() + 1));
 * }
* *

To share references between containers, thus synchronizing changes between * them: * *

{@code
 *   final JsonArray a1 = Json.array(1, 2, 3);
 *   final JsonArray a2 = Json.array(4, 5, 6);
 *
 *   a1.setReference(0, a2.getReference(0));
 *   a1.set(0, 7);
 *
 *   assert Json.array(7, 2, 3).equals(a1);
 *   assert Json.array(7, 5, 6).equals(a2);
 * }
*/ public class JsonReference { protected JsonValue referent; protected boolean accessed; protected boolean mutable; /** * Construct a new reference when given a value to wrap. * *

Note that this value may be null, in which case it will * simply be wrapped as {@link JsonLiteral#jsonNull}. * * @param referent The value being wrapped. */ public JsonReference(final @Nullable JsonValue referent) { this.referent = Json.nonnull(referent); this.accessed = false; this.mutable = true; } /** * Returns the value being wrapped by this object. * *

Calling this method implies that the referent is required by the application * in some way. For example, to be {@link JsonValue#unwrap unwrapped} and treated * as raw data, not JSON data. The alternative would be to {@link #getOnly} * the data, which implies that we are using it to update formatting or simply * find a value which we do need. * *

We call this an "accessing" operation. * *

Quite literally, this means that the value will be flagged as "accessed," * which can be reflected upon at a later time to provide diagnostics to the end * user or to investigate potential optimizations regarding unused values. * * @return The referent */ public @NotNull JsonValue get() { this.accessed = true; return this.getOnly(); } /** * Returns the value being wrapped by this object. * *

Calling this method does not imply that the referent is required by the * application. Instead, the value is being used for the purpose of reflection * . For example, to inspect or update formatting options or scan for matching * values. Philosophically speaking, this means that the value could be removed * without changing the behavior of the application in any significant way. * *

We call this a "visiting" operation. * *

Literally speaking, this operation avoids updating this value's access flags, * which means the value will still be in an "unused" state. This can be reflected * upon at a later time to provide diagnostics to the end user or to investigate * potential optimizations regarding unused values. * * @return The referent */ public JsonValue getOnly() { return this.referent; } /** * Points this reference toward a different {@link JsonValue}. * *

This is an {@link #get accessing} operation. * * @param referent The new referent being wrapped by this object. * @return this, for method chaining. * @throws UnsupportedOperationException If this reference is immutable. * @see #get */ public JsonReference set(final @Nullable JsonValue referent) { this.accessed = true; return this.setOnly(referent); } /** * Visiting counterpart of {@link #set}. * *

This is a {@link #getOnly visiting} operation. * * @param referent The new referent being wrapped by this object. * @return this, for method chaining. * @throws UnsupportedOperationException If this reference is immutable. * @see #getOnly */ public JsonReference setOnly(final @Nullable JsonValue referent) { this.checkMutable(); this.referent = Json.nonnull(referent); return this; } /** * Applies the given transformation to the referent of this object. * *

This is an {@link #get accessing} operation. * * @param updater An expression transforming the wrapped {@link JsonValue}. * @return this, for method chaining. * @throws UnsupportedOperationException If this reference is immutable. * @see #get */ public JsonReference apply(final Function updater) { this.accessed = true; return this.applyOnly(updater); } /** * Visiting counterpart of {@link #apply}. * *

This is a {@link #getOnly visiting} operation. * * @param updater An expression transforming the wrapped {@link JsonValue}. * @return this, for method chaining. * @throws UnsupportedOperationException If this reference is immutable. * @see #getOnly */ public JsonReference applyOnly(final Function updater) { this.checkMutable(); final Object result = updater.apply(this.referent); if (result instanceof JsonValue) { this.referent = (JsonValue) result; } else { this.referent = Json.any(result).setDefaultMetadata(this.referent); } return this; } /** * Indicates whether this reference has been {@link #get accessed}. * * @return true, if the value has been accessed. */ public boolean isAccessed() { return this.accessed; } /** * Overrides the access flag for this reference. * * @param accessed Whether the value has been {@link #get accessed}. * @return this, for method chaining. */ public JsonReference setAccessed(final boolean accessed) { this.accessed = accessed; return this; } /** * Indicates whether this reference may be updated. * * @return true, if the reference may be updated. */ public boolean isMutable() { return this.mutable; } /** * Freezes this reference into an immutable state. * *

This operation is permanent. * * @return this, for method chaining. */ public JsonReference freeze() { this.mutable = false; return this; } private void checkMutable() { if (!this.mutable) { throw new UnsupportedOperationException("Reference is immutable: " + this); } } /** * Generates a mutable clone of this reference. * * @param trackAccess Whether to additionally persist access tracking. * @return A copy of this reference. */ public JsonReference copy(final boolean trackAccess) { final JsonReference clone = new JsonReference(this.referent); return trackAccess ? clone.setAccessed(this.accessed) : clone; } @Override public int hashCode() { int result = 1; result = 31 * result + this.referent.hashCode(); if (this.accessed) result *= 17; if (this.mutable) result *= 31; return result; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o instanceof JsonReference) { final JsonReference other = (JsonReference) o; return this.referent.equals(other.referent) && this.accessed == other.accessed && this.mutable == other.mutable; } return false; } @Override public String toString() { return this.referent.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy