001/*
002 * ============================================================================
003 *  Copyright © 2002-2026 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.internal;
019
020import static java.nio.file.StandardOpenOption.READ;
021import static org.apiguardian.api.API.Status.INTERNAL;
022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
023import static org.tquadrat.foundation.lang.Objects.requireNotBlankArgument;
024import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
025import static org.tquadrat.foundation.util.HexUtils.convertFromHexString;
026import static org.tquadrat.foundation.util.IOUtils.DEFAULT_BUFFER_SIZE;
027
028import java.io.IOException;
029import java.io.Serial;
030import java.nio.file.Files;
031import java.nio.file.Path;
032import java.security.MessageDigest;
033import java.util.Arrays;
034import java.util.HexFormat;
035import java.util.zip.Checksum;
036
037import org.apiguardian.api.API;
038import org.tquadrat.foundation.annotation.ClassVersion;
039import org.tquadrat.foundation.annotation.NotRecord;
040import org.tquadrat.foundation.exception.UnlikelyExceptionError;
041import org.tquadrat.foundation.util.Hash;
042
043/**
044 *  The implementation for the interface
045 *  {@link Hash}.
046 *
047 *  @version $Id: HashImpl.java 1151 2025-10-01 21:32:15Z tquadrat $
048 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
049 *  @UMLGraph.link
050 *  @since 0.1.1
051 */
052@SuppressWarnings( "SerializableDeserializableClassInSecureContext" )
053@ClassVersion( sourceVersion = "$Id: HashImpl.java 1151 2025-10-01 21:32:15Z tquadrat $" )
054@API( status = INTERNAL, since = "0.1.1" )
055@NotRecord
056public final class HashImpl implements Hash
057{
058        /*------------*\
059    ====** Attributes **=======================================================
060        \*------------*/
061    /**
062     *  The hash value.
063     *
064     *  @serial
065     */
066    private final byte [] m_HashValue;
067
068        /*------------------------*\
069    ====** Static Initialisations **===========================================
070        \*------------------------*/
071    /**
072     *  The serial version UID for objects of this class: {@value}.
073     *
074     *  @hidden
075     */
076    @Serial
077    private static final long serialVersionUID = 539879857L;
078
079        /*--------------*\
080    ====** Constructors **=====================================================
081        \*--------------*/
082    /**
083     *  <p>{@summary {@constructor}}</p>
084     *
085     *  @param  hashValue   The hash value.
086     */
087    public HashImpl( final byte [] hashValue )
088    {
089        m_HashValue = requireNotEmptyArgument( hashValue, "hashValue" );
090    }   //  HashImpl()
091
092        /*---------*\
093    ====** Methods **==========================================================
094        \*---------*/
095    /**
096     *  {@inheritDoc}
097     */
098    @Override
099    public final byte [] bytes() { return m_HashValue.clone(); }
100
101    /**
102     *  {@inheritDoc}
103     */
104    @Override
105    public final HashImpl clone()
106    {
107        final HashImpl retValue;
108        try
109        {
110            retValue = (HashImpl) super.clone();
111        }
112        catch( final CloneNotSupportedException e )
113        {
114            throw new UnlikelyExceptionError( e );
115        }
116
117        //---* Done *----------------------------------------------------------
118        return retValue;
119    }   //  clone()
120
121    /**
122     *  Creates the hash for the given byte array, using the given algorithm.
123     *
124     *  @param  data    The input data.
125     *  @param  algorithm   The algorithm
126     *  @return A new instance of {@code HashImpl}.
127     */
128    public static final HashImpl create( final byte [] data, final Checksum algorithm )
129    {
130        requireNonNullArgument( algorithm, "algorithm" ).reset();
131        algorithm.update( requireNonNullArgument( data, "data" ) );
132        final var hashValue = algorithm.getValue();
133        final var retValue = from( Long.toHexString( hashValue ) );
134
135        //---* Done *----------------------------------------------------------
136        return retValue;
137    }   //  create()
138
139    /**
140     *  Creates the hash for the given file, using the given algorithm.
141     *
142     *  @param  data    The input data.
143     *  @param  algorithm   The algorithm
144     *  @return A new instance of {@code HashImpl}.
145     *  @throws IOException Problems to process the file.
146     */
147    public static Hash create( final Path data, final Checksum algorithm ) throws IOException
148    {
149        requireNonNullArgument( algorithm, "algorithm" ).reset();
150        try( final var inputStream = Files.newInputStream( requireNonNullArgument( data, "data" ), READ ) )
151        {
152            final var buffer = new byte [DEFAULT_BUFFER_SIZE];
153            var readBytes = 0;
154            //noinspection NestedAssignment
155            while( (readBytes = inputStream.read( buffer )) > 0 )
156            {
157                algorithm.update( buffer, 0, readBytes );
158            }
159        }
160        final var retValue = from( Long.toHexString( algorithm.getValue() ) );
161
162        //---* Done *----------------------------------------------------------
163        return retValue;
164    }   //  create()
165
166    /**
167     *  Creates the hash for the given byte array, using the given algorithm.
168     *
169     *  @param  data    The input data.
170     *  @param  algorithm   The algorithm
171     *  @return A new instance of {@code HashImpl}.
172     */
173    public static final HashImpl create( final byte [] data, final MessageDigest algorithm )
174    {
175        requireNonNullArgument( algorithm, "algorithm" ).reset();
176        final var hashValue = algorithm.digest( requireNonNullArgument( data, "data" ) );
177        final var retValue = new HashImpl( hashValue );
178
179        //---* Done *----------------------------------------------------------
180        return retValue;
181    }   //  create()
182
183    /**
184     *  Creates the hash for the given file, using the given algorithm.
185     *
186     *  @param  data    The input data.
187     *  @param  algorithm   The algorithm
188     *  @throws IOException Problems to process the file.
189     *  @return A new instance of {@code HashImpl}.
190     */
191    public static Hash create( final Path data, final MessageDigest algorithm ) throws IOException
192    {
193        requireNonNullArgument( algorithm, "algorithm" ).reset();
194        try( final var inputStream = Files.newInputStream( requireNonNullArgument( data, "data" ), READ ) )
195        {
196            final var buffer = new byte [DEFAULT_BUFFER_SIZE];
197            var readBytes = 0;
198            //noinspection NestedAssignment
199            while( (readBytes = inputStream.read( buffer )) > 0 )
200            {
201                algorithm.update( buffer, 0, readBytes );
202            }
203        }
204        final var retValue = new HashImpl( algorithm.digest() );
205
206        //---* Done *----------------------------------------------------------
207        return retValue;
208    }   //  create()
209
210    /**
211     *  {@inheritDoc}
212     */
213    @Override
214    public final boolean equals( final Object o )
215    {
216        var retValue = this == o;
217        if( !retValue && o instanceof final HashImpl other )
218        {
219            retValue = Arrays.equals( m_HashValue, other.m_HashValue );
220        }
221
222        //---* Done *----------------------------------------------------------
223        return retValue;
224    }   //  equals()
225
226    /**
227     *  Creates an instance of {@code Hash} from the given String.
228     *
229     *  @param  hashValue   The hash value.
230     *  @return A new instance of {@code HashImpl}.
231     */
232    public static final HashImpl from( final CharSequence hashValue )
233    {
234        final var retValue = new HashImpl( convertFromHexString( requireNotBlankArgument( hashValue, "hashValue" ) ) );
235
236        //---* Done *----------------------------------------------------------
237        return retValue;
238    }   //  from()
239
240    /**
241     *  {@inheritDoc}
242     */
243    @Override
244    public final int hashCode() { return Arrays.hashCode( m_HashValue ); }
245
246    /**
247     *  {@inheritDoc}
248     */
249    @Override
250    public final String toString() { return HexFormat.of().formatHex( m_HashValue ); }
251}
252//  class HashImpl
253
254/*
255 *  End of File
256 */