001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 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, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.fx.util; 019 020import static org.apiguardian.api.API.Status.STABLE; 021import static org.tquadrat.foundation.lang.Objects.isNull; 022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 023import static org.tquadrat.foundation.lang.Objects.requireNotBlankArgument; 024 025import java.util.Optional; 026import java.util.function.Function; 027import java.util.function.UnaryOperator; 028 029import org.apiguardian.api.API; 030import org.tquadrat.foundation.annotation.ClassVersion; 031import org.tquadrat.foundation.fx.control.ErrorDisplay; 032import org.tquadrat.foundation.lang.GenericStringConverter; 033import org.tquadrat.foundation.lang.Stringer; 034import javafx.util.StringConverter; 035 036/** 037 * <p>{@summary An implementation of 038 * {@link org.tquadrat.foundation.lang.StringConverter org.tquadrat.foundation.lang.StringConverter} 039 * (the <i>Foundation</i> {@code StringConverter}) that extends 040 * {@link StringConverter javafx.util.StringConverter} 041 * (the JavaFX {@code StringConverter}).} It delegates the transformation work 042 * to the <i>Foundation</i> {@code StringConverter} instance that is provided 043 * to the 044 * {@link #FXStringConverter(org.tquadrat.foundation.lang.StringConverter) constructor}.</p> 045 * <p>To reuse an existing JavaFX {@code StringConverter}, you can use the 046 * method 047 * {@link #wrap(StringConverter)}.</p> 048 * <p>To get just a <i>Foundation</i> {@code StringConverter}, you can use 049 * this code (here {@code BigInteger} is used as an example):</p> 050 * <pre><code> 051 * final var x = new BigIntegerStringConverter(); 052 * final var c = new GenericStringConverter<BigInteger>( s -> isNull( s ) ? null : x.fromString( s.toString() ), x::toString ); 053 * </code></pre> 054 * <p>If you need to build a String converter from scratch that should serve 055 * both purposes, write your own class that extends 056 * {@link StringConverter javafx.util.StringConverter} 057 * and implements 058 * {@link org.tquadrat.foundation.lang.StringConverter org.tquadrat.foundation.lang.StringConverter}, 059 * and then implement the methods accordingly. Keep in mind that the method 060 * signature for {@code fromString()} differs for both the abstract class and 061 * the interface because of the different argument type (String vs. 062 * CharSequence).</p> 063 * <p>When a reference to an instance of 064 * {@link org.tquadrat.foundation.fx.control.ErrorDisplay} 065 * is provided to the constructor, an error messsage is displayed when the 066 * {@code fromString()} conversion fails.</p> 067 * 068 * @note The method {@code fromString()} of a JavaFX {@code StringConverter} 069 * may always return {@code null}, for each and every argument, but this 070 * is not allowed for an implementation of the method with the same name 071 * for a <i>Foundation</i> {@code StringConverter}. 072 * 073 * @param <T> The target type for the conversion. 074 * 075 * @version $Id: FXStringConverter.java 1113 2024-03-12 02:01:14Z tquadrat $ 076 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 077 * @UMLGraph.link 078 * @since 0.4.3 079 * 080 * @see GenericStringConverter 081 */ 082@ClassVersion( sourceVersion = "$Id: FXStringConverter.java 1113 2024-03-12 02:01:14Z tquadrat $" ) 083@API( status = STABLE, since = "0.4.3" ) 084public final class FXStringConverter<T> extends StringConverter<T> implements org.tquadrat.foundation.lang.StringConverter<T> 085{ 086 /*------------*\ 087 ====** Attributes **======================================================= 088 \*------------*/ 089 /** 090 * The reference to the error display. 091 */ 092 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 093 private final transient Optional<ErrorDisplay> m_ErrorDisplay; 094 095 /** 096 * The function that composes the error messsage. 097 */ 098 private final transient UnaryOperator<String> m_MessageComposer; 099 100 /** 101 * The message id. 102 */ 103 private final transient String m_MessageId; 104 105 /** 106 * The <i>Foundation</i> 107 * {@link org.tquadrat.foundation.lang.StringConverter StringConverter} 108 * instance. 109 * 110 * @serial 111 */ 112 private final org.tquadrat.foundation.lang.StringConverter<T> m_StringConverter; 113 114 /*--------------*\ 115 ====** Constructors **===================================================== 116 \*--------------*/ 117 /** 118 * Creates a new instance of {@code FXStringConverter}. 119 * 120 * @param stringConverter The <i>Foundation</i> 121 * {@link org.tquadrat.foundation.lang.StringConverter StringConverter} 122 * instance that does the work. 123 */ 124 public FXStringConverter( final org.tquadrat.foundation.lang.StringConverter<T> stringConverter ) 125 { 126 super(); 127 128 m_StringConverter = requireNonNullArgument( stringConverter, "stringConverter" ); 129 m_ErrorDisplay = Optional.empty(); 130 m_MessageComposer = null; 131 m_MessageId = null; 132 } // FXStringConverter() 133 134 /** 135 * Creates a new instance of {@code FXStringConverter}. 136 * 137 * @param stringConverter The <i>Foundation</i> 138 * {@link org.tquadrat.foundation.lang.StringConverter StringConverter} 139 * instance that does the work. 140 * @param errorDisplay The reference to the error display control that 141 * should display the error messages. 142 * @param messageComposer The function that creates the error message to 143 * display. 144 * @param messageId The message id (refer to 145 * {@link ErrorDisplay#addMessage(String, String) ErrorDisplay.addMessage()}). 146 */ 147 public FXStringConverter( final org.tquadrat.foundation.lang.StringConverter<T> stringConverter, final ErrorDisplay errorDisplay, final UnaryOperator<String> messageComposer, final String messageId ) 148 { 149 super(); 150 151 m_StringConverter = requireNonNullArgument( stringConverter, "stringConverter" ); 152 m_ErrorDisplay = Optional.of( requireNonNullArgument( errorDisplay, "errorDisplay" ) ); 153 m_MessageComposer = requireNonNullArgument( messageComposer, "messageComposer" ); 154 m_MessageId = requireNotBlankArgument( messageId, "messageId" ); 155 } // FXStringConverter() 156 157 /*---------*\ 158 ====** Methods **========================================================== 159 \*---------*/ 160 /** 161 * {@inheritDoc} 162 */ 163 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 164 @Override 165 public final T fromString( final String source ) 166 { 167 final var retValue = isNull( source ) ? null : fromString( (CharSequence) source ); 168 169 //---* Done *---------------------------------------------------------- 170 return retValue; 171 } // fromString() 172 173 /** 174 * {@inheritDoc} 175 */ 176 @Override 177 public final T fromString( final CharSequence source ) 178 { 179 T retValue; 180 try 181 { 182 retValue = isNull( source ) ? null : m_StringConverter.fromString( source ); 183 m_ErrorDisplay.ifPresent( errorDisplay -> errorDisplay.removeMessage( m_MessageId ) ); 184 } 185 catch( final IllegalArgumentException e ) 186 { 187 m_ErrorDisplay.ifPresentOrElse( errorDisplay -> errorDisplay.addMessage( m_MessageId, m_MessageComposer.apply( source.toString() ) ), () -> {throw e;} ); 188 retValue = null; 189 } 190 191 //---* Done *---------------------------------------------------------- 192 return retValue; 193 } // fromString() 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 public final String toString( final T source ) 200 { 201 final var retValue = isNull( source ) ? null : m_StringConverter.toString( source ); 202 203 //---* Done *---------------------------------------------------------- 204 return retValue; 205 } // toString() 206 207 /** 208 * <p>{@summary Creates an instance of {@code FXStringConverter} from an 209 * instance of {@link StringConverter javafx.util.StringConverter}.}</p> 210 * <p>Keep in mind that the implementation of the method 211 * {@link StringConverter#fromString(String) javafx.util.StringConverter.fromString()} 212 * may return {@code null} for all input arguments, but that this 213 * behaviour is not valid for an implementation of the method 214 * {@link org.tquadrat.foundation.lang.StringConverter#fromString(CharSequence) org.tquadrat.foundation.lang.StringConverter.fromString()}.</p> 215 * 216 * @param <C> The subject class. 217 * @param stringConverter The instance of 218 * {@link StringConverter}. 219 * @return The new instance. 220 */ 221 public static final <C> FXStringConverter<C> wrap( final StringConverter<C> stringConverter ) 222 { 223 final var stringer = (Stringer<C>) (value -> isNull( value ) ? null : stringConverter.toString( value )); 224 final var parser = (Function<CharSequence, C>) (charSequence -> isNull( charSequence ) ? null : stringConverter.fromString( charSequence.toString() )); 225 226 final var converter = new GenericStringConverter<>( parser, stringer ); 227 228 final var retValue = new FXStringConverter<>( converter ); 229 230 //---* Done *---------------------------------------------------------- 231 return retValue; 232 } // wrap() 233} 234// class FXStringConverter 235 236/* 237 * End of File 238 */