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 java.util.Arrays.stream;
023import static org.apiguardian.api.API.Status.STABLE;
024import static org.tquadrat.foundation.lang.Objects.isNull;
025import static org.tquadrat.foundation.lang.Objects.nonNull;
026import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
027
028import java.util.Collection;
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 *  <p>{@summary The abstract base class for implementations of
041 *  {@link PreferenceAccessor}
042 *  for instances of implementations of
043 *  {@link Collection}.}</p>
044 *
045 *  @note   This class requires that there is an implementation of
046 *      {@code StringConverter} available for the collection's component type.
047 *
048 *  @param  <T> The component type of the {@code Collection}.
049 *  @param  <C> The type of the {@code Collection}.
050 *
051 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
052 *  @version $Id: CollectionAccessor.java 1061 2023-09-25 16:32:43Z tquadrat $
053 *  @since 0.0.1
054 *
055 *  @UMLGraph.link
056 */
057@ClassVersion( sourceVersion = "$Id: CollectionAccessor.java 1061 2023-09-25 16:32:43Z tquadrat $" )
058@API( status = STABLE, since = "0.0.1" )
059public abstract sealed class CollectionAccessor<T,C extends Collection<T>> extends PreferenceAccessor<C>
060    permits ListAccessor, SetAccessor
061{
062        /*------------*\
063    ====** Attributes **=======================================================
064        \*------------*/
065    /**
066     *  The instance of
067     *  {@link StringConverter}
068     *  that is used to convert the collection component values.
069     */
070    private final StringConverter<T> m_StringConverter;
071
072        /*--------------*\
073    ====** Constructors **=====================================================
074        \*--------------*/
075    /**
076     *  Creates a new {@code CollectionAccessor} instance.
077     *
078     *  @param  propertyName    The name of the property.
079     *  @param  stringConverter The implementation of
080     *      {@link StringConverter}
081     *      for the component type.
082     *  @param  getter  The property getter.
083     *  @param  setter  The property setter.
084     */
085    protected CollectionAccessor( final String propertyName, final StringConverter<T> stringConverter, final Getter<C> getter, final Setter<C> setter )
086    {
087        super( propertyName, getter, setter );
088        m_StringConverter = requireNonNullArgument( stringConverter, "stringConverter" );
089    }   //  CollectionAccessor()
090
091        /*---------*\
092    ====** Methods **==========================================================
093        \*---------*/
094    /**
095     *  <p>{@summary Converts the given String to an instance of the property
096     *  type.}</p>
097     *  <p>This implementation uses
098     *  {@link StringConverter#fromString(CharSequence)}
099     *  for the conversion.</p>
100     *
101     *  @param  node The {@code Preferences} node that provides the value.
102     *  @param  index   The value index.
103     *  @param  s   The String value; can be {@code null}.
104     *  @return The value instance; will be {@code null} if the provided
105     *      String is {@code null} or cannot be converted to the type of the
106     *      property.
107     *  @throws InvalidPreferenceValueException The preferences value cannot be
108     *      translated to the property type.
109     */
110    protected final T fromString( final Preferences node, final int index, final String s ) throws InvalidPreferenceValueException
111    {
112        final T retValue;
113        try
114        {
115            retValue = m_StringConverter.fromString( s );
116        }
117        catch( final IllegalArgumentException e )
118        {
119            throw new InvalidPreferenceValueException( node, format( "%s:%d", getPropertyName(), index ), s, e );
120        }
121
122        //---* Done *----------------------------------------------------------
123        return retValue;
124    }   //  fromString()
125
126    /**
127     *  Creates the collection data structure.
128     *
129     *  @param  size    The suggested size of the collection; can be ignored if
130     *      not appropriate.
131     *  @return The collection data structure.
132     */
133    protected abstract C createCollection( final int size );
134
135    /**
136     *  {@inheritDoc}
137     */
138    @Override
139    public final void readPreference( final Preferences node ) throws BackingStoreException, InvalidPreferenceValueException
140    {
141        requireNonNullArgument( node, "node" );
142        final var propertyName = getPropertyName();
143        var collection = getter().get();
144        if( node.nodeExists( propertyName ) )
145        {
146            final var childNode = node.node( propertyName );
147            final var keys = stream( childNode.keys() )
148                .filter( key -> key.startsWith( propertyName ) )
149                .toArray( String []::new );
150            collection = createCollection( keys.length );
151            for( var i = 0; i < keys.length; ++i )
152            {
153                final var value = fromString( childNode, i, childNode.get( keys [i], null ) );
154                if( !isNull( value ) ) collection.add( value );
155            }
156        }
157        setter().set( collection );
158    }   //  readPreferences()
159
160    /**
161     *  <p>{@summary Converts the given instance of the property type to a
162     *  String.}</p>
163     *  <p>This implementation uses
164     *  {@link StringConverter#toString(Object)}
165     *  for the conversion.</p>
166     *
167     *  @param  t   The property value; can be {@code null}.
168     *  @return The String implementation; will be {@code null} if the provided
169     *      value is {@code null} or cannot be converted to a String.
170     */
171    protected final String toString( final T t )
172    {
173        final var retValue = m_StringConverter.toString( t );
174
175        //---* Done *----------------------------------------------------------
176        return retValue;
177    }   //  toString()
178
179    /**
180     *  {@inheritDoc}
181     */
182    @Override
183    public final void writePreference( final Preferences node ) throws BackingStoreException
184    {
185        requireNonNullArgument( node, "node" );
186        final var propertyName = getPropertyName();
187        final var collection = getter().get();
188        if( isNull( collection ) )
189        {
190            if( node.nodeExists( propertyName ) ) node.node( propertyName ).removeNode();
191        }
192        else
193        {
194            final var childNode = node.node( propertyName );
195            var counter = 0;
196            for( final var element : collection )
197            {
198                final var value = toString( element );
199                if( nonNull( value ) ) childNode.put( format( "%s[%d]", propertyName, ++counter ), value );
200            }
201        }
202    }   //  writePreferences()
203}
204//  class CollectionAccessor
205
206/*
207 *  End of File
208 */