001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.settings;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.util.*;
023import java.util.function.*;
024
025import org.apache.juneau.commons.function.Snippet;
026
027/**
028 * A writable {@link SettingStore} implementation created from functional interfaces.
029 *
030 * <p>
031 * This class allows you to create writable setting stores from lambda expressions or method references,
032 * making it easy to wrap existing property systems (e.g., custom configuration systems) as
033 * {@link SettingStore} instances.
034 *
035 * <h5 class='section'>Return Value Semantics:</h5>
036 * <ul class='spaced-list'>
037 *    <li>If the reader function returns <c>null</c>, this store returns <c>null</c> (key doesn't exist).
038 *    <li>If the reader function returns a non-null value, this store returns <c>Optional.of(value)</c>.
039 * </ul>
040 *
041 * <p>
042 * Note: This store cannot distinguish between a key that doesn't exist and a key that exists with a null value,
043 * since the reader function only returns a <c>String</c>. If you need to distinguish these cases, use {@link MapStore} instead.
044 *
045 * <h5 class='section'>Example:</h5>
046 * <p class='bjava'>
047 *    <jc>// Create a writable functional store</jc>
048 *    FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
049 *       System::getProperty,  <jc>// reader</jc>
050 *       (k, v) -&gt; System.setProperty(k, v),  <jc>// writer</jc>
051 *       k -&gt; System.clearProperty(k),  <jc>// unset</jc>
052 *       () -&gt; { <jc>// clear</jc>
053 *          <jc>// Clear all properties logic</jc>
054 *       }
055 *    );
056 *
057 *    <jc>// Use it</jc>
058 *    <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
059 *    Optional&lt;String&gt; <jv>value</jv> = <jv>store</jv>.get(<js>"my.property"</js>);
060 *    <jv>store</jv>.unset(<js>"my.property"</js>);
061 * </p>
062 */
063public class FunctionalStore implements SettingStore {
064
065   private final Function<String, String> reader;
066   private final BiConsumer<String, String> writer;
067   private final Consumer<String> unsetter;
068   private final Snippet clearer;
069
070   /**
071    * Creates a new writable functional store.
072    *
073    * @param reader The function to read property values. Must not be <c>null</c>.
074    * @param writer The function to write property values. Must not be <c>null</c>.
075    * @param unsetter The function to remove property values. Must not be <c>null</c>.
076    * @param clearer The snippet to clear all property values. Must not be <c>null</c>.
077    */
078   public FunctionalStore(
079      Function<String, String> reader,
080      BiConsumer<String, String> writer,
081      Consumer<String> unsetter,
082      Snippet clearer
083   ) {
084      assertArgNotNull("reader", reader);
085      assertArgNotNull("writer", writer);
086      assertArgNotNull("unsetter", unsetter);
087      assertArgNotNull("clearer", clearer);
088      this.reader = reader;
089      this.writer = writer;
090      this.unsetter = unsetter;
091      this.clearer = clearer;
092   }
093
094   /**
095    * Returns a setting by applying the reader function.
096    *
097    * <p>
098    * If the reader function returns <c>null</c>, this method returns <c>null</c> (indicating the key doesn't exist).
099    * If the reader function returns a non-null value, this method returns <c>Optional.of(value)</c>.
100    *
101    * @param name The property name.
102    * @return The property value, or <c>null</c> if the reader function returns <c>null</c>.
103    */
104   @Override
105   public Optional<String> get(String name) {
106      var v = reader.apply(name);
107      return v == null ? null : opt(v);
108   }
109
110   /**
111    * Sets a setting by applying the writer function.
112    *
113    * @param name The property name.
114    * @param value The property value, or <c>null</c> to set an empty override.
115    */
116   @Override
117   public void set(String name, String value) {
118      writer.accept(name, value);
119   }
120
121   /**
122    * Removes a setting by applying the unsetter function.
123    *
124    * @param name The property name to remove.
125    */
126   @Override
127   public void unset(String name) {
128      unsetter.accept(name);
129   }
130
131   /**
132    * Clears all settings by invoking the clearer snippet.
133    *
134    * <p>
135    * If the clearer snippet throws an exception, it will be wrapped in a {@link RuntimeException}.
136    */
137   @Override
138   public void clear() {
139      safe(()->clearer.run());
140   }
141
142   /**
143    * Creates a writable functional store from four functions.
144    *
145    * <p>
146    * This is a convenience factory method for creating writable functional stores.
147    *
148    * <h5 class='section'>Example:</h5>
149    * <p class='bjava'>
150    *    <jc>// Create from lambdas</jc>
151    *    FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
152    *       System::getProperty,
153    *       (k, v) -&gt; System.setProperty(k, v),
154    *       k -&gt; System.clearProperty(k),
155    *       () -&gt; { <jc>// Clear all properties</jc> }
156    *    );
157    * </p>
158    *
159    * @param reader The function to read property values. Must not be <c>null</c>.
160    * @param writer The function to write property values. Must not be <c>null</c>.
161    * @param unsetter The function to remove property values. Must not be <c>null</c>.
162    * @param clearer The snippet to clear all property values. Must not be <c>null</c>.
163    * @return A new writable functional store instance.
164    */
165   public static FunctionalStore of(
166      Function<String, String> reader,
167      BiConsumer<String, String> writer,
168      Consumer<String> unsetter,
169      Snippet clearer
170   ) {
171      return new FunctionalStore(reader, writer, unsetter, clearer);
172   }
173}
174