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.Utils.*;
020
021import java.util.*;
022import java.util.concurrent.*;
023import java.util.concurrent.atomic.*;
024
025/**
026 * A writable {@link SettingStore} implementation backed by a thread-safe map.
027 *
028 * <p>
029 * This class provides a mutable store for settings that can be modified at runtime. It's particularly useful
030 * for creating custom property stores (e.g., Spring properties, configuration files) that can be added to
031 * {@link Settings} as sources.
032 *
033 * <h5 class='section'>Thread Safety:</h5>
034 * <p>
035 * This class is thread-safe. The internal map is lazily initialized using {@link AtomicReference} and
036 * {@link ConcurrentHashMap} for thread-safe operations.
037 *
038 * <h5 class='section'>Null Value Handling:</h5>
039 * <p>
040 * Setting a value to <c>null</c> stores <c>Optional.empty()</c> in the map, which means {@link #get(String)}
041 * will return <c>Optional.empty()</c> (not <c>null</c>). This allows you to explicitly override system properties
042 * with null values. Use {@link #unset(String)} if you want to remove a key entirely (so {@link #get(String)} returns <c>null</c>).
043 *
044 * <h5 class='section'>Example:</h5>
045 * <p class='bjava'>
046 *    <jc>// Create a store and add properties</jc>
047 *    MapStore <jv>store</jv> = <jk>new</jk> MapStore();
048 *    <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
049 *    <jv>store</jv>.set(<js>"another.property"</js>, <js>"another-value"</js>);
050 *
051 *    <jc>// Add to Settings as a source (stores can be used as sources)</jc>
052 *    Settings.<jsf>get</jsf>().addSource(<jv>store</jv>);
053 *
054 *    <jc>// Override a system property with null</jc>
055 *    <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
056 *    <jc>// get() will now return Optional.empty() for "system.property"</jc>
057 *
058 *    <jc>// Remove a property entirely</jc>
059 *    <jv>store</jv>.unset(<js>"my.property"</js>);
060 *    <jc>// get() will now return null for "my.property"</jc>
061 * </p>
062 */
063public class MapStore implements SettingStore {
064
065   private final AtomicReference<Map<String,Optional<String>>> map = new AtomicReference<>();
066
067   /**
068    * Returns a setting from this store.
069    *
070    * <p>
071    * Returns <c>null</c> if the key doesn't exist in the map, or the stored value (which may be
072    * <c>Optional.empty()</c> if the value was explicitly set to <c>null</c>).
073    *
074    * @param key The property name.
075    * @return The property value, <c>null</c> if the key doesn't exist, or <c>Optional.empty()</c> if the key
076    *    exists but has a null value.
077    */
078   @Override
079   public Optional<String> get(String key) {
080      var m = map.get();
081      if (m == null)
082         return null;
083      return m.get(key);
084   }
085
086   /**
087    * Sets a setting in this store.
088    *
089    * <p>
090    * The internal map is lazily initialized on the first call to this method. Setting a value to <c>null</c>
091    * stores <c>Optional.empty()</c> in the map, which means {@link #get(String)} will return <c>Optional.empty()</c>
092    * (not <c>null</c>). This allows you to explicitly override system properties with null values.
093    *
094    * @param key The property name.
095    * @param value The property value, or <c>null</c> to set an empty override.
096    */
097   @Override
098   public void set(String key, String value) {
099      var m = map.get();
100      if (m == null) {
101         var newMap = new ConcurrentHashMap<String,Optional<String>>();
102         m = map.compareAndSet(null, newMap) ? newMap : map.get();  // Not easily testable.
103      }
104      m.put(key, opt(value));
105   }
106
107   /**
108    * Clears all entries from this store.
109    *
110    * <p>
111    * After calling this method, all keys will be removed from the map, and {@link #get(String)} will return
112    * <c>null</c> for all keys.
113    */
114   @Override
115   public void clear() {
116      var m = map.get();
117      if (m != null)
118         m.clear();
119   }
120
121   /**
122    * Removes a setting from this store.
123    *
124    * <p>
125    * After calling this method, {@link #get(String)} will return <c>null</c> for the specified key,
126    * indicating that the key doesn't exist in this store (as opposed to returning <c>Optional.empty()</c>,
127    * which would indicate the key exists but has a null value).
128    *
129    * @param name The property name to remove.
130    */
131   @Override
132   public void unset(String name) {
133      var m = map.get();
134      if (m != null)
135         m.remove(name);
136   }
137
138}