]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Introduce utility class org.simantics.db.function.DbOptional<T>. 70/170/2
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 25 Nov 2016 10:03:27 +0000 (12:03 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 25 Nov 2016 13:49:05 +0000 (15:49 +0200)
The class is a direct copy of java.util.Optional<T> but it uses
DbConsumer, DbFunction, DbPredicate and DbSupplier instead of the
standard Java 1.8 functional interfaces to allow the DbOptional methods
to throw DatabaseExceptions for cleaner database code in certain
situations.

The rationale behind this class is much the same as with the standard
optional. See e.g.
http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html.

refs #6829

Change-Id: Ibf62a56eb227264cbfac359aca99d87570b68a47

bundles/org.simantics.db/src/org/simantics/db/function/DbOptional.java [new file with mode: 0644]

diff --git a/bundles/org.simantics.db/src/org/simantics/db/function/DbOptional.java b/bundles/org.simantics.db/src/org/simantics/db/function/DbOptional.java
new file mode 100644 (file)
index 0000000..a15cb11
--- /dev/null
@@ -0,0 +1,333 @@
+package org.simantics.db.function;\r
+\r
+import java.util.NoSuchElementException;\r
+import java.util.Objects;\r
+\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+/**\r
+ * A container object which may or may not contain a non-null value. If a value\r
+ * is present, {@code isPresent()} will return {@code true} and {@code get()}\r
+ * will return the value.\r
+ *\r
+ * <p>\r
+ * Additional methods that depend on the presence or absence of a contained\r
+ * value are provided, such as {@link #orElse(java.lang.Object) orElse()}\r
+ * (return a default value if value not present) and\r
+ * {@link #ifPresent(org.simantics.db.function.DbConsumer) ifPresent()} (execute\r
+ * a block of code if the value is present).\r
+ *\r
+ * <p>\r
+ * This is a value-based class; use of identity-sensitive operations (including\r
+ * reference equality ({@code ==}), identity hash code, or synchronization) on\r
+ * instances of {@code DbOptional} may have unpredictable results and should be\r
+ * avoided.\r
+ *\r
+ * @since 1.25\r
+ */\r
+public final class DbOptional<T> {\r
+    /**\r
+     * Common instance for {@code empty()}.\r
+     */\r
+    private static final DbOptional<?> EMPTY = new DbOptional<>();\r
+\r
+    /**\r
+     * If non-null, the value; if null, indicates no value is present\r
+     */\r
+    private final T value;\r
+\r
+    /**\r
+     * Constructs an empty instance.\r
+     *\r
+     * @implNote Generally only one empty instance, {@link Optional#EMPTY},\r
+     * should exist per VM.\r
+     */\r
+    private DbOptional() {\r
+        this.value = null;\r
+    }\r
+\r
+    /**\r
+     * Returns an empty {@code Optional} instance.  No value is present for this\r
+     * Optional.\r
+     *\r
+     * @apiNote Though it may be tempting to do so, avoid testing if an object\r
+     * is empty by comparing with {@code ==} against instances returned by\r
+     * {@code Option.empty()}. There is no guarantee that it is a singleton.\r
+     * Instead, use {@link #isPresent()}.\r
+     *\r
+     * @param <T> Type of the non-existent value\r
+     * @return an empty {@code Optional}\r
+     */\r
+    public static<T> DbOptional<T> empty() {\r
+        @SuppressWarnings("unchecked")\r
+        DbOptional<T> t = (DbOptional<T>) EMPTY;\r
+        return t;\r
+    }\r
+\r
+    /**\r
+     * Constructs an instance with the value present.\r
+     *\r
+     * @param value the non-null value to be present\r
+     * @throws NullPointerException if value is null\r
+     */\r
+    private DbOptional(T value) {\r
+        this.value = Objects.requireNonNull(value);\r
+    }\r
+\r
+    /**\r
+     * Returns an {@code Optional} with the specified present non-null value.\r
+     *\r
+     * @param <T> the class of the value\r
+     * @param value the value to be present, which must be non-null\r
+     * @return an {@code Optional} with the value present\r
+     * @throws NullPointerException if value is null\r
+     */\r
+    public static <T> DbOptional<T> of(T value) {\r
+        return new DbOptional<>(value);\r
+    }\r
+\r
+    /**\r
+     * Returns an {@code Optional} describing the specified value, if non-null,\r
+     * otherwise returns an empty {@code Optional}.\r
+     *\r
+     * @param <T> the class of the value\r
+     * @param value the possibly-null value to describe\r
+     * @return an {@code Optional} with a present value if the specified value\r
+     * is non-null, otherwise an empty {@code Optional}\r
+     */\r
+    public static <T> DbOptional<T> ofNullable(T value) {\r
+        return value == null ? empty() : of(value);\r
+    }\r
+\r
+    /**\r
+     * If a value is present in this {@code Optional}, returns the value,\r
+     * otherwise throws {@code NoSuchElementException}.\r
+     *\r
+     * @return the non-null value held by this {@code Optional}\r
+     * @throws NoSuchElementException if there is no value present\r
+     *\r
+     * @see Optional#isPresent()\r
+     */\r
+    public T get() {\r
+        if (value == null) {\r
+            throw new NoSuchElementException("No value present");\r
+        }\r
+        return value;\r
+    }\r
+\r
+    /**\r
+     * Return {@code true} if there is a value present, otherwise {@code false}.\r
+     *\r
+     * @return {@code true} if there is a value present, otherwise {@code false}\r
+     */\r
+    public boolean isPresent() {\r
+        return value != null;\r
+    }\r
+\r
+    /**\r
+     * If a value is present, invoke the specified consumer with the value,\r
+     * otherwise do nothing.\r
+     *\r
+     * @param consumer block to be executed if a value is present\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if value is present and {@code consumer} is\r
+     * null\r
+     */\r
+    public void ifPresent(DbConsumer<? super T> consumer) throws DatabaseException {\r
+        if (value != null)\r
+            consumer.accept(value);\r
+    }\r
+\r
+    /**\r
+     * If a value is present, and the value matches the given predicate,\r
+     * return an {@code Optional} describing the value, otherwise return an\r
+     * empty {@code Optional}.\r
+     *\r
+     * @param predicate a predicate to apply to the value, if present\r
+     * @return an {@code Optional} describing the value of this {@code Optional}\r
+     * if a value is present and the value matches the given predicate,\r
+     * otherwise an empty {@code Optional}\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if the predicate is null\r
+     */\r
+    public DbOptional<T> filter(DbPredicate<? super T> predicate) throws DatabaseException {\r
+        Objects.requireNonNull(predicate);\r
+        if (!isPresent())\r
+            return this;\r
+        else\r
+            return predicate.test(value) ? this : empty();\r
+    }\r
+\r
+    /**\r
+     * If a value is present, apply the provided mapping function to it,\r
+     * and if the result is non-null, return an {@code Optional} describing the\r
+     * result.  Otherwise return an empty {@code Optional}.\r
+     *\r
+     * @apiNote This method supports post-processing on optional values, without\r
+     * the need to explicitly check for a return status.  For example, the\r
+     * following code traverses a stream of file names, selects one that has\r
+     * not yet been processed, and then opens that file, returning an\r
+     * {@code Optional<FileInputStream>}:\r
+     *\r
+     * <pre>{@code\r
+     *     Optional<FileInputStream> fis =\r
+     *         names.stream().filter(name -> !isProcessedYet(name))\r
+     *                       .findFirst()\r
+     *                       .map(name -> new FileInputStream(name));\r
+     * }</pre>\r
+     *\r
+     * Here, {@code findFirst} returns an {@code Optional<String>}, and then\r
+     * {@code map} returns an {@code Optional<FileInputStream>} for the desired\r
+     * file if one exists.\r
+     *\r
+     * @param <U> The type of the result of the mapping function\r
+     * @param mapper a mapping function to apply to the value, if present\r
+     * @return an {@code Optional} describing the result of applying a mapping\r
+     * function to the value of this {@code Optional}, if a value is present,\r
+     * otherwise an empty {@code Optional}\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if the mapping function is null\r
+     */\r
+    public<U> DbOptional<U> map(DbFunction<? super T, ? extends U> mapper) throws DatabaseException {\r
+        Objects.requireNonNull(mapper);\r
+        if (!isPresent())\r
+            return empty();\r
+        else {\r
+            return DbOptional.ofNullable(mapper.apply(value));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * If a value is present, apply the provided {@code Optional}-bearing\r
+     * mapping function to it, return that result, otherwise return an empty\r
+     * {@code Optional}.  This method is similar to {@link #map(Function)},\r
+     * but the provided mapper is one whose result is already an {@code Optional},\r
+     * and if invoked, {@code flatMap} does not wrap it with an additional\r
+     * {@code Optional}.\r
+     *\r
+     * @param <U> The type parameter to the {@code Optional} returned by\r
+     * @param mapper a mapping function to apply to the value, if present\r
+     *           the mapping function\r
+     * @return the result of applying an {@code Optional}-bearing mapping\r
+     * function to the value of this {@code Optional}, if a value is present,\r
+     * otherwise an empty {@code Optional}\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if the mapping function is null or returns\r
+     * a null result\r
+     */\r
+    public<U> DbOptional<U> flatMap(DbFunction<? super T, DbOptional<U>> mapper) throws DatabaseException {\r
+        Objects.requireNonNull(mapper);\r
+        if (!isPresent())\r
+            return empty();\r
+        else {\r
+            return Objects.requireNonNull(mapper.apply(value));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Return the value if present, otherwise return {@code other}.\r
+     *\r
+     * @param other the value to be returned if there is no value present, may\r
+     * be null\r
+     * @return the value, if present, otherwise {@code other}\r
+     */\r
+    public T orElse(T other) {\r
+        return value != null ? value : other;\r
+    }\r
+\r
+    /**\r
+     * Return the value if present, otherwise invoke {@code other} and return\r
+     * the result of that invocation.\r
+     *\r
+     * @param other a {@code Supplier} whose result is returned if no value\r
+     * is present\r
+     * @return the value if present otherwise the result of {@code other.get()}\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if value is not present and {@code other} is\r
+     * null\r
+     */\r
+    public T orElseGet(DbSupplier<? extends T> other) throws DatabaseException {\r
+        return value != null ? value : other.get();\r
+    }\r
+\r
+    /**\r
+     * Return the contained value, if present, otherwise throw an exception\r
+     * to be created by the provided supplier.\r
+     *\r
+     * @apiNote A method reference to the exception constructor with an empty\r
+     * argument list can be used as the supplier. For example,\r
+     * {@code IllegalStateException::new}\r
+     *\r
+     * @param <X> Type of the exception to be thrown\r
+     * @param exceptionSupplier The supplier which will return the exception to\r
+     * be thrown\r
+     * @return the present value\r
+     * @throws X if there is no value present\r
+     * @throws DatabaseException \r
+     * @throws NullPointerException if no value is present and\r
+     * {@code exceptionSupplier} is null\r
+     */\r
+    public <X extends Throwable> T orElseThrow(DbSupplier<? extends X> exceptionSupplier) throws X, DatabaseException {\r
+        if (value != null) {\r
+            return value;\r
+        } else {\r
+            throw exceptionSupplier.get();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Indicates whether some other object is "equal to" this Optional. The\r
+     * other object is considered equal if:\r
+     * <ul>\r
+     * <li>it is also an {@code Optional} and;\r
+     * <li>both instances have no value present or;\r
+     * <li>the present values are "equal to" each other via {@code equals()}.\r
+     * </ul>\r
+     *\r
+     * @param obj an object to be tested for equality\r
+     * @return {code true} if the other object is "equal to" this object\r
+     * otherwise {@code false}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj) {\r
+        if (this == obj) {\r
+            return true;\r
+        }\r
+\r
+        if (!(obj instanceof DbOptional)) {\r
+            return false;\r
+        }\r
+\r
+        DbOptional<?> other = (DbOptional<?>) obj;\r
+        return Objects.equals(value, other.value);\r
+    }\r
+\r
+    /**\r
+     * Returns the hash code value of the present value, if any, or 0 (zero) if\r
+     * no value is present.\r
+     *\r
+     * @return hash code value of the present value or 0 if no value is present\r
+     */\r
+    @Override\r
+    public int hashCode() {\r
+        return Objects.hashCode(value);\r
+    }\r
+\r
+    /**\r
+     * Returns a non-empty string representation of this Optional suitable for\r
+     * debugging. The exact presentation format is unspecified and may vary\r
+     * between implementations and versions.\r
+     *\r
+     * @implSpec If a value is present the result must include its string\r
+     * representation in the result. Empty and present DbOptionals must be\r
+     * unambiguously differentiable.\r
+     *\r
+     * @return the string representation of this instance\r
+     */\r
+    @Override\r
+    public String toString() {\r
+        return value != null\r
+            ? String.format("DbOptional[%s]", value)\r
+            : "DbOptional.empty";\r
+    }\r
+}\r