001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 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.util.stringconverter; 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; 026import static org.tquadrat.foundation.util.StringUtils.isEmptyOrBlank; 027 028import java.io.IOException; 029import java.io.ObjectInputStream; 030import java.io.ObjectOutputStream; 031import java.io.Serial; 032import java.time.format.DateTimeFormatter; 033import java.time.format.DateTimeParseException; 034import java.time.temporal.Temporal; 035import java.time.temporal.TemporalAccessor; 036import java.util.Collection; 037import java.util.List; 038import java.util.Optional; 039 040import org.apiguardian.api.API; 041import org.tquadrat.foundation.annotation.ClassVersion; 042import org.tquadrat.foundation.annotation.MountPoint; 043import org.tquadrat.foundation.lang.StringConverter; 044 045/** 046 * <p>{@summary The abstract base class for implementations of 047 * {@link StringConverter} 048 * for types that extend 049 * {@link TemporalAccessor}.}</p> 050 * <p>The format for the date/time data can be modified by applying an 051 * instance of 052 * {@link java.time.format.DateTimeFormatter} 053 * to the constructor 054 * {@link #TimeDateStringConverter(Class,DateTimeFormatter)} 055 * that is used for parsing Strings to object instances and for converting 056 * object instances to Strings.</p> 057 * 058 * @param <T> The type that is handled by this class. 059 * 060 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 061 * @version $Id: TimeDateStringConverter.java 1130 2024-05-05 16:16:09Z tquadrat $ 062 * @since 0.0.6 063 * 064 * @UMLGraph.link 065 * 066 * @see DateTimeFormatter 067 */ 068@ClassVersion( sourceVersion = "$Id: TimeDateStringConverter.java 1130 2024-05-05 16:16:09Z tquadrat $" ) 069@API( status = STABLE, since = "0.0.6" ) 070public abstract class TimeDateStringConverter<T extends TemporalAccessor> implements StringConverter<T> 071{ 072 /*-----------*\ 073 ====** Constants **======================================================== 074 \*-----------*/ 075 /** 076 * The error message for an invalid date/time on the command line: {@value}. 077 */ 078 public static final String MSG_InvalidDateTimeFormat = "'%1$s' cannot be parsed as a valid date/time"; 079 080 /*------------*\ 081 ====** Attributes **======================================================= 082 \*------------*/ 083 /** 084 * The formatter that is used to format the date/time data. 085 */ 086 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 087 private final transient Optional<DateTimeFormatter> m_Formatter; 088 089 /** 090 * The subject class for this converter. 091 * 092 * @serial 093 */ 094 private final Class<T> m_SubjectClass; 095 096 /*------------------------*\ 097 ====** Static Initialisations **=========================================== 098 \*------------------------*/ 099 /** 100 * The serial version UID for objects of this class: {@value}. 101 * 102 * @hidden 103 */ 104 @Serial 105 private static final long serialVersionUID = 1L; 106 107 /*--------------*\ 108 ====** Constructors **===================================================== 109 \*--------------*/ 110 /** 111 * Creates a new {@code TimeStringConverter} instance. 112 * 113 * @param subjectClass The subject class. 114 */ 115 protected TimeDateStringConverter( final Class<T> subjectClass ) { this( subjectClass, Optional.empty()); } 116 117 /** 118 * Creates a new {@code TimeStringConverter} instance that uses the given 119 * formatter for the conversion back and forth. 120 * 121 * @note The formatter may not drop any part of the Zoned date time, 122 * otherwise {@code fromString()} may fail. This means that the 123 * formatter is only allowed to re-order the temporal fields. 124 * 125 * @param subjectClass The subject class. 126 * @param formatter The formatter for the date/time data. 127 */ 128 protected TimeDateStringConverter( final Class<T> subjectClass, final DateTimeFormatter formatter ) 129 { 130 this( subjectClass, Optional.of( requireNonNullArgument( formatter, "formatter" ) ) ); 131 } // TimeDateStringConverter() 132 133 /** 134 * Creates a new {@code TimeStringConverter} instance. 135 * 136 * @param subjectClass The subject class. 137 * @param formatter The formatter for the date/time data. 138 */ 139 private TimeDateStringConverter( final Class<T> subjectClass, @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) final Optional<DateTimeFormatter> formatter ) 140 { 141 m_SubjectClass = subjectClass; 142 m_Formatter = formatter; 143 } // TimeDateStringConverter() 144 145 /*---------*\ 146 ====** Methods **========================================================== 147 \*---------*/ 148 /** 149 * {@inheritDoc} 150 */ 151 @Override 152 public final T fromString( final CharSequence source ) throws IllegalArgumentException 153 { 154 T retValue = null; 155 if( nonNull( source ) ) 156 { 157 if( isEmptyOrBlank( source ) ) throw new IllegalArgumentException( format( MSG_InvalidDateTimeFormat, source ) ); 158 try 159 { 160 retValue = parseDateTime( source, m_Formatter ); 161 } 162 catch( final DateTimeParseException e ) 163 { 164 throw new IllegalArgumentException( format( MSG_InvalidDateTimeFormat, source ), e ); 165 } 166 } 167 168 //---* Done *---------------------------------------------------------- 169 return retValue; 170 } // fromString() 171 172 /** 173 * Provides the subject class for this converter. 174 * 175 * @return The subject class. 176 */ 177 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 178 public final Collection<Class<T>> getSubjectClass() { return List.of( m_SubjectClass ); } 179 180 /** 181 * Parses the given String to an instance of 182 * {@link Temporal}. 183 * The caller ensures that {@code source} is not {@code null}, not the 184 * empty String and does not contain only whitespace. 185 * 186 * @param source The String to parse. 187 * @param formatter The formatter for parsing the String 188 * @return The time/date value. 189 * @throws DateTimeParseException The given value cannot be parsed to a 190 * {@code Temporal}. 191 */ 192 @MountPoint 193 protected abstract T parseDateTime( CharSequence source, @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) Optional<DateTimeFormatter> formatter ) throws DateTimeParseException; 194 195 /** 196 * Loads a previously serialised instance of this class from the given 197 * input stream. 198 * 199 * @param in The input stream. 200 * @throws IOException The de-serialisation failed. 201 * @throws ClassNotFoundException A class could not be found. 202 */ 203 @Serial 204 @SuppressWarnings( "static-method" ) 205 private final void readObject( final ObjectInputStream in ) throws IOException, ClassNotFoundException 206 { 207 in.defaultReadObject(); 208 } // readObject() 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override 214 public final String toString( final T source ) 215 { 216 final var retValue = isNull( source ) ? null : m_Formatter.map( formatter -> formatter.format( source ) ).orElse( source.toString() ); 217 218 //---* Done *---------------------------------------------------------- 219 return retValue; 220 } // toString() 221 222 /** 223 * Writes a serialised instance of this class to the given output stream. 224 * This fails if a 225 * {@link DateTimeFormatter} 226 * instance was assigned to this instance. 227 * 228 * @param out The output stream. 229 * @throws IOException A {@code DateTimeFormatter} was assigned to this 230 * instance. 231 */ 232 @Serial 233 private final void writeObject( final ObjectOutputStream out) throws IOException 234 { 235 if( m_Formatter.isPresent() ) throw new IOException( "Cannot serialize instance of '%s' with DateTimeFormatter set".formatted( getClass().getName() ) ); 236 out.defaultWriteObject(); 237 } // writeObject() 238} 239// class TimeDateStringConverter 240 241/* 242 * End of File 243 */