001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * 007 * Licensed to the public under the agreements of the GNU Lesser General Public 008 * License, version 3.0 (the "License"). You may obtain a copy of the License at 009 * 010 * http://www.gnu.org/licenses/lgpl.html 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 015 * License for the specific language governing permissions and limitations 016 * under the License. 017 */ 018 019package org.tquadrat.foundation.config.spi.prefs; 020 021import static java.lang.String.format; 022import static org.apiguardian.api.API.Status.STABLE; 023import static org.tquadrat.foundation.lang.Objects.isNull; 024import static org.tquadrat.foundation.lang.Objects.nonNull; 025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 026 027import java.util.HashMap; 028import java.util.Map; 029import java.util.prefs.BackingStoreException; 030import java.util.prefs.Preferences; 031 032import org.apiguardian.api.API; 033import org.tquadrat.foundation.annotation.ClassVersion; 034import org.tquadrat.foundation.config.spi.InvalidPreferenceValueException; 035import org.tquadrat.foundation.function.Getter; 036import org.tquadrat.foundation.function.Setter; 037import org.tquadrat.foundation.lang.StringConverter; 038 039/** 040 * The implementation of 041 * {@link org.tquadrat.foundation.config.spi.prefs.PreferenceAccessor} 042 * for instances of 043 * {@link Map}. 044 * 045 * @note This class requires that there is an implementation of 046 * {@code StringConverter} available for both the {@code Map}'s key and 047 * value types. 048 * 049 * @param <K> The key type of the {@code Map}. 050 * @param <V> The value type of the {@code Map}. 051 * 052 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 053 * @version $Id: MapAccessor.java 1061 2023-09-25 16:32:43Z tquadrat $ 054 * @since 0.0.1 055 * 056 * @UMLGraph.link 057 */ 058@ClassVersion( sourceVersion = "$Id: MapAccessor.java 1061 2023-09-25 16:32:43Z tquadrat $" ) 059@API( status = STABLE, since = "0.0.1" ) 060public final class MapAccessor<K,V> extends PreferenceAccessor<Map<K,V>> 061{ 062 /*------------*\ 063 ====** Attributes **======================================================= 064 \*------------*/ 065 /** 066 * The instance of 067 * {@link StringConverter} 068 * that is used to convert the key values. 069 */ 070 private final StringConverter<K> m_KeyStringConverter; 071 072 /** 073 * The instance of 074 * {@link StringConverter} 075 * that is used to convert the key values. 076 */ 077 private final StringConverter<V> m_ValueStringConverter; 078 079 /*--------------*\ 080 ====** Constructors **===================================================== 081 \*--------------*/ 082 /** 083 * Creates a new {@code MapAccessor} instance. 084 * 085 * @param propertyName The name of the property. 086 * @param keyStringConverter The implementation of 087 * {@link StringConverter} 088 * for the key type. 089 * @param valueStringConverter The implementation of 090 * {@link StringConverter} 091 * for the value type. 092 * @param getter The property getter. 093 * @param setter The property setter. 094 */ 095 public MapAccessor( final String propertyName, final StringConverter<K> keyStringConverter, final StringConverter<V> valueStringConverter, final Getter<Map<K,V>> getter, final Setter<Map<K,V>> setter ) 096 { 097 super( propertyName, getter, setter ); 098 m_KeyStringConverter = requireNonNullArgument( keyStringConverter, "keyStringConverter" ); 099 m_ValueStringConverter = requireNonNullArgument( valueStringConverter, "valueStringConverter" ); 100 } // MapAccessor() 101 102 /*---------*\ 103 ====** Methods **========================================================== 104 \*---------*/ 105 /** 106 * Converts the given {@code Map<String,String>} to a {@code Map<K,V>}. 107 * 108 * @param node The {@code Preferences} node that provides the key and 109 * value. 110 * @param map The input map; can be {@code null}. 111 * @return The converted map; will be {@code null} if the provided map was 112 * {@code null}. 113 * @throws InvalidPreferenceValueException The preferences value cannot be 114 * translated to the property type. 115 */ 116 private final Map<K,V> fromStringMap( final Preferences node, final Map<String,String> map ) throws InvalidPreferenceValueException 117 { 118 requireNonNullArgument( node, "node" ); 119 Map<K,V> retValue = null; 120 if( nonNull( map ) ) 121 { 122 retValue = new HashMap<>(); 123 ConversionLoop: for( final var entry : map.entrySet() ) 124 { 125 final K keyInstance; 126 final V valueInstance; 127 128 final var key = entry.getKey(); 129 final var value = entry.getValue(); 130 try 131 { 132 keyInstance = m_KeyStringConverter.fromString( key ); 133 } 134 catch( final IllegalArgumentException e ) 135 { 136 throw new InvalidPreferenceValueException( node, format( "%s:(%s:%s)", getPropertyName(), key, value ), key, e ); 137 } 138 try 139 { 140 valueInstance = m_ValueStringConverter.fromString( value ); 141 } 142 catch( final IllegalArgumentException e ) 143 { 144 throw new InvalidPreferenceValueException( node, format( "%s:(%s:%s)", getPropertyName(), key, value ), value, e ); 145 } 146 if( nonNull( keyInstance ) && nonNull( valueInstance ) ) 147 { 148 retValue.put( keyInstance, valueInstance ); 149 } 150 } // ConversionLoop: 151 } 152 153 //---* Done *---------------------------------------------------------- 154 return retValue; 155 } // fromStringMap() 156 157 158 /** 159 * {@inheritDoc} 160 */ 161 @Override 162 public final void readPreference( final Preferences node ) throws BackingStoreException, InvalidPreferenceValueException 163 { 164 requireNonNullArgument( node, "node" ); 165 final var propertyName = getPropertyName(); 166 var map = getter().get(); 167 if( node.nodeExists( propertyName ) ) 168 { 169 final var childNode = node.node( propertyName ); 170 final var keys = childNode.keys(); 171 final Map<String,String> stringMap = new HashMap<>(); 172 for( final var key : keys ) 173 { 174 final var value = childNode.get( key, null ); 175 if( nonNull( value ) ) stringMap.put( key, value ); 176 } 177 map = fromStringMap( childNode, stringMap ); 178 } 179 setter().set( map ); 180 } // readPreferences() 181 182 /** 183 * Converts the given {@code Map<K,V>} to a {@code Map<String,String>}. 184 * 185 * @param map The input map. 186 * @return The converted map. 187 */ 188 private final Map<String,String> toStringMap( final Map<? extends K, ? extends V> map ) 189 { 190 Map<String,String> retValue = null; 191 if( nonNull( map ) ) 192 { 193 retValue = new HashMap<>(); 194 for( final var entry : map.entrySet() ) 195 { 196 final var key = m_KeyStringConverter.toString( entry.getKey() ); 197 final var value = m_ValueStringConverter.toString( entry.getValue() ); 198 if( nonNull( key ) && nonNull( value ) ) retValue.put( key, value ); 199 } 200 } 201 202 //---* Done *---------------------------------------------------------- 203 return retValue; 204 } // toStringMap() 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public final void writePreference( final Preferences node ) throws BackingStoreException 211 { 212 final var propertyName = getPropertyName(); 213 final var valueMap = toStringMap( getter().get() ); 214 if( isNull( valueMap ) ) 215 { 216 if( node.nodeExists( propertyName ) ) node.node( propertyName ).removeNode(); 217 } 218 else 219 { 220 final var childNode = node.node( propertyName ); 221 for( final var entry : valueMap.entrySet() ) 222 { 223 childNode.put( entry.getKey(), entry.getValue() ); 224 } 225 } 226 } // writePreferences() 227} 228// class MapAccessor 229 230/* 231 * End of File 232 */