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.Shorts.*;
020
021import java.util.*;
022import java.util.concurrent.*;
023import java.util.concurrent.atomic.*;
024
025/**
026 * A writable {@link PropertyStore} 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 a {@link PropertyLookupResult#present(Optional)} result with an empty optional.
042 * This allows you to explicitly override system properties with null values.
043 * Use {@link #unset(String)} if you want to remove a key entirely (so {@link #get(String)} returns {@link PropertyLookupResult#missing()}).
044 *
045 * <h5 class='section'>Example:</h5>
046 * <p class='bjava'>
047 *    <jc>// Create a store and add properties</jc>
048 *    MapStore <jv>store</jv> = <jk>new</jk> MapStore();
049 *    <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
050 *    <jv>store</jv>.set(<js>"another.property"</js>, <js>"another-value"</js>);
051 *
052 *    <jc>// Add to Settings as a source (stores can be used as sources)</jc>
053 *    Settings.<jsf>get</jsf>().addSource(<jv>store</jv>);
054 *
055 *    <jc>// Override a system property with null</jc>
056 *    <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
057 *    <jc>// get() will now return present(Optional.empty()) for "system.property"</jc>
058 *
059 *    <jc>// Remove a property entirely</jc>
060 *    <jv>store</jv>.unset(<js>"my.property"</js>);
061 *    <jc>// get() will now return missing() for "my.property"</jc>
062 * </p>
063 */
064public class MapStore implements PropertyStore {
065
066   private final AtomicReference<Map<String,Optional<String>>> map = new AtomicReference<>();
067
068   /**
069    * Returns a setting from this store.
070    *
071    * <p>
072    * Returns {@link PropertyLookupResult#missing()} if the key doesn't exist in the map, or a present result
073    * (which may contain {@link Optional#empty()} if the value was explicitly set to <c>null</c>).
074    *
075    * @param key The property name.
076    * @return The property lookup result.
077    */
078   @Override
079   public PropertyLookupResult get(String key) {
080      var m = map.get();
081      if (m == null)
082         return PropertyLookupResult.missing();
083      if (!m.containsKey(key))
084         return PropertyLookupResult.missing();
085      return PropertyLookupResult.present(m.get(key));
086   }
087
088   /**
089    * Sets a setting in this store.
090    *
091    * <p>
092    * The internal map is lazily initialized on the first call to this method. Setting a value to <c>null</c>
093    * stores <c>Optional.empty()</c> in the map, which means {@link #get(String)} will return a present result
094    * with an empty optional. This allows you to explicitly override system properties with null values.
095    *
096    * @param key The property name.
097    * @param value The property value, or <c>null</c> to set an empty override.
098    */
099   @Override
100   public void set(String key, String value) {
101      var m = map.get();
102      if (m == null) {
103         var newMap = new ConcurrentHashMap<String,Optional<String>>();
104         m = map.compareAndSet(null, newMap) ? newMap : map.get();  // Not easily testable.
105      }
106      m.put(key, o(value));
107   }
108
109   /**
110    * Clears all entries from this store.
111    *
112    * <p>
113    * After calling this method, all keys will be removed from the map, and {@link #get(String)} will return
114    * {@link PropertyLookupResult#missing()} for all keys.
115    */
116   @Override
117   public void clear() {
118      var m = map.get();
119      if (m != null)
120         m.clear();
121   }
122
123   /**
124    * Removes a setting from this store.
125    *
126    * <p>
127    * After calling this method, {@link #get(String)} will return {@link PropertyLookupResult#missing()} for
128    * the specified key, indicating that the key doesn't exist in this store.
129    *
130    * @param name The property name to remove.
131    */
132   @Override
133   public void unset(String name) {
134      var m = map.get();
135      if (m != null)
136         m.remove(name);
137   }
138
139}