001/*
002 * ============================================================================
003 * Copyright © 2002-2024 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;
019
020import static org.apiguardian.api.API.Status.STABLE;
021import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
022
023import java.util.HexFormat;
024
025import org.apiguardian.api.API;
026import org.tquadrat.foundation.annotation.ClassVersion;
027import org.tquadrat.foundation.annotation.UtilityClass;
028import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
029import org.tquadrat.foundation.exception.ValidationException;
030
031/**
032 *  <p>{@summary A set of utility methods that are useful for the conversion of
033 *  byte arrays to and from strings of hexadecimal digits.}</p>
034 *  <p>Parts of the code were adopted from the class
035 *  {@code org.apache.catalina.util.HexUtils} (written by Craig R. McClanahan)
036 *  out of the Tomcat source and modified to match the requirements of this
037 *  project.</p>
038 *  <p>Partially, the methods got obsolete with the introduction of
039 *  {@link java.util.HexFormat}
040 *  in Java&nbsp;17.</p>
041 *
042 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
043 *  @thanks Craig R. McClanahan
044 *  @version $Id: HexUtils.java 1086 2024-01-05 23:18:33Z tquadrat $
045 *  @since 0.0.5
046 *
047 *  @UMLGraph.link
048 */
049@ClassVersion( sourceVersion = "$Id: HexUtils.java 1086 2024-01-05 23:18:33Z tquadrat $" )
050@UtilityClass
051public final class HexUtils
052{
053        /*--------------*\
054    ====** Constructors **=====================================================
055        \*--------------*/
056    /**
057     *  No instance of this class is allowed.
058     */
059    private HexUtils() { throw new PrivateConstructorForStaticClassCalledError( HexUtils.class ); }
060
061        /*---------*\
062    ====** Methods **==========================================================
063        \*---------*/
064    /**
065     *  <p>{@summary Converts a String of hexadecimal digits into the
066     *  corresponding byte array by encoding each two hexadecimal digits as a
067     *  byte.} The method will not distinguish between upper or lower case for
068     *  the digit from {@code 0xA} to {@code 0xF}.</p>
069     *  <p>If the number of digits in the String is odd, the String will be
070     *  <i>prefixed</i> by an additional 0.</p>
071     *
072     *  @param  digits  Hexadecimal digits representation.
073     *  @return The resulting byte array.
074     *  @throws IllegalArgumentException The input String is {@code null},
075     *      empty, or it contains an invalid character that cannot be
076     *      interpreted as a hexadecimal digit.
077     */
078    @API( status = STABLE, since = "0.0.5" )
079    public static byte[] convertFromHexString( final CharSequence digits )
080    {
081        final var len = requireNotEmptyArgument( digits, "digits" ).length();
082        final var buffer = new StringBuilder( len + 1 );
083        if( len % 2 > 0 ) buffer.append( '0' );
084        buffer.append( digits );
085        final var retValue = HexFormat.of().parseHex( buffer );
086
087        //---* Done *----------------------------------------------------------
088        return retValue;
089    }   //  convertFromHexString()
090
091    /**
092     *  Converts an array of hexadecimal digits into the corresponding byte
093     *  array by encoding each two hexadecimal digits as a byte. The method
094     *  will not distinguish between upper or lower case for the digit from
095     *  {@code 0xA} to {@code 0xF}.<br>
096     *  <br>If the number of digits in the array is odd, an additional 0 will
097     *  <i>prepended</i> to it.
098     *
099     *  @param  digits  Hexadecimal digits representation.
100     *  @return The resulting byte array.
101     *  @throws IllegalArgumentException The input array is {@code null},
102     *      empty, or it contains an invalid character that cannot be
103     *      interpreted as a hexadecimal digit.
104     */
105    @SuppressWarnings( "MethodCanBeVariableArityMethod" )
106    @API( status = STABLE, since = "0.0.5" )
107    public static final byte[] convertFromHexCharArray( final char [] digits )
108    {
109        final var retValue = convertFromHexString( new String( requireNotEmptyArgument( digits, "digits" ) ) );
110
111        //---* Done *----------------------------------------------------------
112        return retValue;
113    }   //  convertFromHexCharArray()
114
115    /**
116     *  Converts an integer in the range form 0 to 15 to a hex digit.
117     *
118     *  @param  value   The value to convert.
119     *  @return The hex digit (uppercase).
120     */
121    @SuppressWarnings( "MagicNumber" )
122    @API( status = STABLE, since = "0.0.5" )
123    public static final char convertToHexDigit( final int value )
124    {
125        if( (value < 0) || (value > 15) )
126        {
127            throw new ValidationException( "The value %1$d cannot be converted to a single HexDigit".formatted( value ) );
128        }
129
130        final var retValue = HexFormat.of().withUpperCase().toLowHexDigit( value );
131
132        //---* Done *----------------------------------------------------------
133        return retValue;
134    }   //  convertToHexDigit()
135}
136//  class HexUtils
137
138/*
139 *  End of File
140 */