com.zepben.util.scoping.Scope Maven / Gradle / Ivy
/*
* Copyright 2020 Zeppelin Bend Pty Ltd
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.zepben.util.scoping;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Class that can run a block of code within a "scope" of a resource.
*
* It basically wraps up the following pattern:
*
* {@code
* resource.acquire();
* try {
* // do some work while the resource is held
* } finally {
* resource.release();
* }
* }
*
*
* You might argue that pattern is fine, however the niceness of this class comes from the
* {@link Scope#supply(Function)} method. In the above try/finally syntax, it gets messy if you want to calculate a
* value while the resource is held, then release the resource to use the value. You either need to release the
* resource in the try block (and either double release due to the finally block or track it has already been released)
* or declare a variable outside the try block and use it after.
*
* Which is nicer?
*
* Stopping double release:
*
* {@code
* bool released = false;
* resource.acquire();
* try {
* int nextValue = resource.nextValue();
* resource.release();
* released = true;
*
* // do something with nextValue
* } finally {
* if (!released) {
* resource.release();
* }
* }
* }
*
*
* Declaring outside try
*
* {@code
* int nextValue;
* resource.acquire();
* try {
* nextValue = resource.nextValue();
* } finally {
* resource.release();
* }
*
* // do something with nextValue
* }
*
*
* or
*
* {@code
* // As a member of your class:
* Scope scope = Scope.alwaysAcquires(resource, resource::acquire, resource::release);
*
* // In a method acquiring the resource
* int nextValue = scope.supply(res -> res.nextValue());
* // do something with nextValue
* }
*
*
*
* @param The type of resource held by the scope.
*/
@SuppressWarnings("WeakerAccess")
public class Scope {
private final T resource;
private final Predicate enter;
private final Consumer exit;
/**
* Create a new Scope instance.
*
* The {@code enter} is a {@link Predicate} that takes the resources. If the resource can be acquired, this should
* return true, otherwise false.
*
* The {@code exit} consumer is used to release your resource after the scope block has been executed.
*
* @param resource The resource to acquire / release.
* @param enter callback to acquire the resource.
* @param exit callback to release the resource.
*/
public Scope(T resource, Predicate enter, Consumer exit) {
this.resource = resource;
this.enter = enter;
this.exit = exit;
}
/**
* Creates a new instance that can always acquire the resource.
*
* Because it can always acquire, the {@code enter} is a supplier that is then wrapped in a predicate that always
* returns true.
*
* This factory method will allow you to instantiate instances in a concise fashion, e.g.:
*
* {@code
* Scope scope = Scope.alwaysAcquires(resource, resource::acquire, resource::release);
* }
*
*
*
* @param resource The resource to acquire / release.
* @param enter callback to acquire the resource.
* @param exit callback to release the resource.
* @param The type of resource held by the scope.
* @return A new scope instance where the resource can always be acquired.
*/
public static Scope alwaysAcquires(T resource, Consumer enter, Consumer exit) {
return new Scope<>(
resource,
r -> {
enter.accept(r);
return true;
},
exit);
}
/**
* Returns the resource contained by the scope.
*
* @return The resource contained by this scope.
*/
public T resource() {
return resource;
}
/**
* Acquires the resource, executes the provided code, and releases the resource when finished.
*
* If the resource can not be acquired, the provided code block will never be executed.
*
* @param block The code to be executed within the scope.
* @return true if the resource was acquired, otherwise false.
*/
public boolean run(Consumer block) {
if (enter.test(resource)) {
try {
block.accept(resource);
return true;
} finally {
exit.accept(resource);
}
}
return false;
}
/**
* Behaves the same as {@link Scope#run(Consumer)}, except returns a value returned by the provided
* code block.
*
* If the resource cannot be acquired, the block is never executed and null is returned.
*
* @param block The code to be executed within the scope, returning a value to be returned by this method.
* @param The return type of the code block.
* @return The return value of the supplied block if the resource was acquired, otherwise null.
*/
@Nullable
public R supply(Function block) {
if (enter.test(resource)) {
try {
return block.apply(resource);
} finally {
exit.accept(resource);
}
}
return null;
}
/**
* Behaves exactly the same as {@link Scope#supply(Function)} but the result is wrapped in an {@link Optional}.
*
* @param block The code to be executed within the scope, returning a value to be returned by this method.
* @param The return type of the code block.
* @return The return value of the supplied block, wrapped in an Optional.
*/
public Optional supplyOptionally(Function block) {
return Optional.ofNullable(supply(block));
}
/**
* Allows use of this scope in a try-with-resources block. This may be useful when using lambdas or method
* references is undesirable for some reason.
*
* The {@link ScopeTwr} returned is an {@link AutoCloseable} that holds the resource of the scope. You can check if
* the resource was required by calling {@link ScopeTwr#acquiredResource()} within the TWR block. If the resource
* was not acquired, it will not attempt to close it at the end of the TWR block.
*
{@code
* try (ScopeTwr twr = scope.twr()) {
* if (twr.acquiredResource()) {
* String id = twr.resource().nextId();
* ...
* }
* }
* }
*
* @return A {@link ScopeTwr} instance containing the resource of this scope.
*/
public ScopeTwr twr() {
return new ScopeTwr<>(resource, enter.test(resource), exit);
}
}