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