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.util.stringconverter; 019 020import static org.apiguardian.api.API.Status.STABLE; 021import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 022import static org.tquadrat.foundation.lang.Objects.isNull; 023import static org.tquadrat.foundation.lang.Objects.nonNull; 024 025import java.io.Serial; 026import java.util.stream.IntStream; 027 028import org.apiguardian.api.API; 029import org.tquadrat.foundation.annotation.ClassVersion; 030import org.tquadrat.foundation.lang.StringConverter; 031 032/** 033 * <p>{@summary An implementation of 034 * {@link StringConverter} 035 * for text values.}</p> 036 * <p>Although technically, a text is a String and therefore a conversion to 037 * String would be redundant (at best), semantically there is difference, and 038 * this implementation of {@code StringConverter} takes care of that.</p> 039 * <p>Other than for the conversions performed by 040 * {@link StringStringConverter}, 041 * the results are not identical with the input values: a text may contain 042 * several special characters, like new lines, tabs, backspaces or form feeds. 043 * These will be translated by the 044 * {@link #toString(String)} 045 * method into escape sequences, 046 * while 047 * {@link #fromString(CharSequence)} 048 * will translate the escape sequences back to the special characters, 049 * according to the table below.</p> 050 * <table border="1"> 051 * <caption>Special Characters and their escape sequences</caption> 052 * <thead> 053 * <tr> 054 * <th>Name</th><th>Code</th><th>Escape</th><th>Comment</th> 055 * </tr> 056 * </thead> 057 * <tbody> 058 * <tr> 059 * <td>backspace</td><td>U+0008</td><td>\b</td><td> </td> 060 * </tr> 061 * <tr> 062 * <td>horizontal tab</td><td>U+0009</td><td>\t</td><td> </td> 063 * </tr> 064 * <tr> 065 * <td>line feed</td><td>U+000A</td><td>\n</td><td>The UNIX line termination, also used in Java internally as the new-line character</td> 066 * </tr> 067 * <tr> 068 * <td>form feed</td><td>U+000C</td><td>\f</td><td>Rarely used</td> 069 * </tr> 070 * <tr> 071 * <td>carriage return</td><td>U+000D</td><td>\r</td><td>The Windows line termination is CRLF or \r\n</td> 072 * </tr> 073 * <tr> 074 * <td>space</td><td>U+0020</td><td>\s</td><td>A space or blank will be escaped only if it is the very first or the last character of a text</td> 075 * </tr> 076 * </tbody> 077 * </table> 078 * 079 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 080 * @version $Id: TextStringConverter.java 1060 2023-09-24 19:21:40Z tquadrat $ 081 * @UMLGraph.link 082 * @since 0.1.0 083 */ 084@ClassVersion( sourceVersion = "$Id: TextStringConverter.java 1060 2023-09-24 19:21:40Z tquadrat $" ) 085@API( status = STABLE, since = "0.1.0" ) 086public final class TextStringConverter implements StringConverter<String> 087{ 088 /*------------------------*\ 089 ====** Static Initialisations **=========================================== 090 \*------------------------*/ 091 /** 092 * The serial version UID for objects of this class: {@value}. 093 * 094 * @hidden 095 */ 096 @Serial 097 private static final long serialVersionUID = 1L; 098 099 /** 100 * An instance of this class. 101 */ 102 public static final TextStringConverter INSTANCE = new TextStringConverter(); 103 104 /*--------------*\ 105 ====** Constructors **===================================================== 106 \*--------------*/ 107 /** 108 * Creates a new instance of {@code TextStringConverter}. 109 */ 110 public TextStringConverter() {} 111 112 /*---------*\ 113 ====** Methods **========================================================== 114 \*---------*/ 115 /** 116 * Returns the escape sequence for the given special character, or the 117 * character itself if it does not need an escape. 118 * 119 * @param c The character. 120 * @return The escape sequence. 121 */ 122 private final IntStream escape( @SuppressWarnings( "StandardVariableNames" ) final int c ) 123 { 124 final var retValue = switch( c ) 125 { 126 case '\b' -> IntStream.of( '\\', 'b' ); 127 case '\t' -> IntStream.of( '\\', 't' ); 128 case '\n' -> IntStream.of( '\\', 'n' ); 129 case '\f' -> IntStream.of( '\\', 'f' ); 130 case '\r' -> IntStream.of( '\\', 'r' ); 131 case '\\' -> IntStream.of( '\\', '\\' ); 132 default -> c < 0x20 133 ? IntStream.concat( IntStream.of( '\\' ), Integer.toString( c, 8 ).codePoints() ) 134 : IntStream.of( c ); 135 }; 136 137 //---* Done *---------------------------------------------------------- 138 return retValue; 139 } // escape() 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public final String fromString( final CharSequence source ) throws IllegalArgumentException 146 { 147 final var retValue = isNull( source ) ? null : source.toString().translateEscapes(); 148 149 //---* Done *---------------------------------------------------------- 150 return retValue; 151 } // fromString() 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public final String toString( final String source ) 158 { 159 String retValue = null; 160 if( nonNull( source ) ) 161 { 162 var text = source; 163 var prefix = EMPTY_STRING; 164 var suffix = EMPTY_STRING; 165 if( text.startsWith( " " ) ) 166 { 167 prefix = "\\s"; 168 text = text.length() > 1 ? text.substring( 1 ) : EMPTY_STRING; 169 } 170 if( text.endsWith( " " ) ) 171 { 172 suffix = "\\s"; 173 text = text.substring( 0, text.length() - 1 ); 174 } 175 final var codePoints = text.codePoints() 176 .flatMap( this::escape ) 177 .toArray(); 178 retValue = prefix + new String( codePoints, 0, codePoints.length ) + suffix; 179 } 180 181 //---* Done *---------------------------------------------------------- 182 return retValue; 183 } // toString() 184} 185// class TextStringConverter 186 187/* 188 * End of File 189 */