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