001/*
002 * ============================================================================
003 * Copyright © 2002-2026 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 *
007 * Licensed to the public under the agreements of the GNU Lesser General Public
008 * License, version 3.0 (the "License"). You may obtain a copy of the License at
009 *
010 *      http://www.gnu.org/licenses/lgpl.html
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015 * License for the specific language governing permissions and limitations
016 * under the License.
017 */
018
019package org.tquadrat.foundation.perflog;
020
021import static java.util.Arrays.asList;
022import static org.apiguardian.api.API.Status.STABLE;
023import static org.tquadrat.foundation.lang.Objects.isNull;
024import static org.tquadrat.foundation.lang.Objects.nonNull;
025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
026import static org.tquadrat.foundation.lang.Objects.requireValidArgument;
027import static org.tquadrat.foundation.perflog.PerfLogUtils.createPerformanceSectionName;
028import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.IGNORED;
029import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.SEND_REPORT_FOR_ABORT;
030import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD;
031import static org.tquadrat.foundation.util.StringUtils.mapFromEmpty;
032import static org.tquadrat.foundation.value.Time.MILLISECOND;
033
034import java.util.EnumSet;
035import java.util.Optional;
036import java.util.OptionalLong;
037import java.util.Set;
038import java.util.concurrent.locks.ReentrantReadWriteLock;
039
040import org.apiguardian.api.API;
041import org.tquadrat.foundation.annotation.ClassVersion;
042import org.tquadrat.foundation.exception.ValidationException;
043import org.tquadrat.foundation.lang.AutoLock;
044import org.tquadrat.foundation.lang.AutoLock.ExecutionFailedException;
045import org.tquadrat.foundation.value.TimeValue;
046
047/**
048 *  <p>{@summary This class describes a &quot;Performance Section&quot;.}</p>
049 *
050 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
051 *  @version $Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $
052 *  @since 0.25.0
053 *
054 *  @UMLGraph.link
055 */
056@ClassVersion( sourceVersion = "$Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $" )
057@API( status = STABLE, since = "0.25.0" )
058public final class PerformanceSection
059{
060        /*---------------*\
061    ====** Inner Classes **====================================================
062        \*---------------*/
063    /**
064     *  <p>{@summary The ignore status for a performance section.}</p>
065     *
066     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
067     *  @version $Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $
068     *  @since 0.25.0
069     *
070     *  @UMLGraph.link
071     */
072    @ClassVersion( sourceVersion = "$Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $" )
073    @API( status = STABLE, since = "0.25.0" )
074    public static enum PerformanceSectionFlags
075    {
076        /**
077         *  The performance section will be ignored.
078         */
079        IGNORED,
080
081        /**
082         *  Indicates that a report should be sent also for aborted performance
083         *  trackers.
084         */
085        SEND_REPORT_FOR_ABORT,
086
087        /**
088         *  Indicates that reports should be sent only when the threshold was
089         *  exceeded.
090         */
091        @SuppressWarnings( "FieldNamingConvention" ) SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD
092    }
093    //  enum PerformanceSectionFlags
094
095        /*------------*\
096    ====** Attributes **=======================================================
097        \*------------*/
098    /**
099     *  The description for the performance section.
100     */
101    private final String m_Description;
102
103    /**
104     *  The flog that controls whether this performance section is ignored.
105     */
106    private boolean m_Ignore;
107
108    /**
109     *  The flag that controls whether reports for this performance section are
110     *  sent in case of an abort.
111     */
112    private boolean m_SendReportForAbort;
113
114    /**
115     *  The flag that controls whether reports for this performance section are
116     *  sent only when the threshold was exceeded.
117     */
118    @SuppressWarnings( "FieldNamingConvention" )
119    private boolean m_SendReportOnlyForExceededThreshold;
120
121    /**
122     *  The unique name of the performance section.
123     */
124    private final PerformanceSectionName m_Name;
125
126    /**
127     *  The guard for read operations.
128     */
129    private final AutoLock m_ReadGuard;
130
131    /**
132     *  <p>{@summary The threshold time.} A value of {@null} means that
133     *  there is no threshold for this performance section.</p>
134     */
135    @SuppressWarnings( "UnusedAssignment" )
136    private TimeValue m_Threshold = null;
137
138    /**
139     *  <p>{@summary The timeout time.} A value of {@null} means that
140     *  there is no timeout for this performance section.</p>
141     */
142    @SuppressWarnings( "UnusedAssignment" )
143    private TimeValue m_Timeout = null;
144
145    /**
146     *  The guard for write operations.
147     */
148    private final AutoLock m_WriteGuard;
149
150        /*--------------*\
151    ====** Constructors **=====================================================
152        \*--------------*/
153    /**
154     *  Creates a new instance of {@code PerformanceSection}.
155     *
156     *  @param  name    The unique name of the performance section.
157     *  @param  description The description for the performance section; can be
158     *      {@null}.
159     *  @param  threshold   The threshold time in milliseconds; a value of
160     *      {@null} means that no threshold was defined for this
161     *      performance section. A value less than 1 is invalid and causes a
162     *      {@link org.tquadrat.foundation.exception.ValidationException}
163     *      to be thrown.
164     *  @param  timeout The timeout time in milliseconds; a value of
165     *      {@null} means that no timeout was defined for this performance
166     *      section. A value less than 1 is invalid and causes a
167     *      {@link org.tquadrat.foundation.exception.ValidationException}
168     *      to be thrown. The timeout value must be greater than the threshold
169     *      value, if provided.
170     *  @param  flags   Provides the configuration for this performance
171     *      section.
172     */
173    public PerformanceSection( final String name, final String description, final Long threshold, final Long timeout, final PerformanceSectionFlags... flags )
174    {
175        this(
176            createPerformanceSectionName( name ),
177            description,
178            isNull( threshold ) ? null : new TimeValue( MILLISECOND, requireValidArgument( threshold, "threshold", t -> t > 0L ) ),
179            isNull( timeout ) ? null : new TimeValue( MILLISECOND, requireValidArgument( timeout, "timeout", t -> t > 0L ) ),
180            flags
181        );
182    }   //  PerformanceSection()
183
184    /**
185     *  Creates a new instance of {@code PerformanceSection}.
186     *
187     *  @param  name    The unique name of the performance section.
188     *  @param  description The description for the performance section; can be
189     *      {@null}.
190     *  @param  threshold   The threshold time; a value of {@null} means
191     *      that no threshold was defined for this performance section. A value
192     *      of 0 is invalid and causes a
193     *      {@link org.tquadrat.foundation.exception.ValidationException}
194     *      to be thrown.
195     *  @param  timeout The timeout time; a value of {@null} means that
196     *      no timeout was defined for this performance section. A value of 0
197     *      is invalid and causes a
198     *      {@link org.tquadrat.foundation.exception.ValidationException}
199     *      to be thrown. The timeout value must be greater than the threshold
200     *      value, if provided.
201     *  @param  flags   Provides the configuration for this performance
202     *      section.
203     */
204    public PerformanceSection( final PerformanceSectionName name, final String description, final TimeValue threshold, final TimeValue timeout, final PerformanceSectionFlags... flags )
205    {
206        m_Description = mapFromEmpty( description, "?" );
207        final Set<PerformanceSectionFlags> flagsSet = EnumSet.noneOf( PerformanceSectionFlags.class );
208        flagsSet.addAll( asList( requireNonNullArgument( flags, "flags" ) ) );
209        m_Ignore = flagsSet.contains( IGNORED );
210        m_Name = requireNonNullArgument( name, "name" );
211        m_SendReportForAbort = flagsSet.contains( SEND_REPORT_FOR_ABORT );
212        m_SendReportOnlyForExceededThreshold = flagsSet.contains( SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD );
213        m_Threshold = validateThresholdAndTimeout( threshold, threshold, null );
214        m_Timeout = validateThresholdAndTimeout( timeout, threshold, timeout );
215
216        final var lock = new ReentrantReadWriteLock();
217        m_ReadGuard = AutoLock.of( lock.readLock() );
218        m_WriteGuard = AutoLock.of( lock.writeLock() );
219    }   //  PerformanceSection()
220
221        /*---------*\
222    ====** Methods **==========================================================
223        \*---------*/
224    /**
225     * {@inheritDoc}
226     * <p>Two instances of {@code PerformanceSection} are equal if their names
227     * are equal.</p>
228     */
229    @Override
230    public final boolean equals( final Object o )
231    {
232        var retValue = this == o;
233        if( !retValue && o instanceof final PerformanceSection other )
234        {
235            retValue = getName().equals( other.getName() );
236        }
237
238        //---* Done *----------------------------------------------------------
239        return retValue;
240    }   //  equals()
241
242    /**
243     *  Returns the description for the performance section.
244     *
245     *  @return The description for the performance section.
246     */
247    public final String getDescription() { return m_Description; }
248
249    /**
250     *  Returns the name of the performance section.
251     *
252     *  @return The name of the performance section.
253     */
254    public final PerformanceSectionName getName() { return m_Name; }
255
256    /**
257     *  Returns the threshold for this performance section.
258     *
259     *  @return An instance of
260     *      {@link OptionalLong}
261     *      that holds the threshold in milliseconds.
262     */
263    public final Optional<TimeValue> getThreshold()
264    {
265        final var retValue = m_ReadGuard.execute( () -> m_Threshold );
266
267        //---* Done *----------------------------------------------------------
268        return retValue;
269    }   //  getThreshold()
270
271    /**
272     *  Returns the timeout for this performance section.
273     *
274     *  @return An instance of
275     *      {@link OptionalLong}
276     *      that holds the timeout in milliseconds.
277     */
278    public final Optional<TimeValue> getTimeout()
279    {
280        final var retValue = m_ReadGuard.execute( () -> m_Timeout );
281
282        //---* Done *----------------------------------------------------------
283        return retValue;
284    }   //  getTimeout()
285
286    /**
287     * {@inheritDoc}
288     * <p>Two instances of {@code PerformanceSection} are equal if their names
289     * are equal.</p>
290     */
291    @Override
292    public final int hashCode() { return getName().hashCode(); }
293
294    /**
295     *  Returns the flag that controls whether this performance section is
296     *  ignored.
297     *
298     *  @return {@true} if the performance section is currently ignored,
299     *      {@false} if the performance section is currently observed.
300     */
301    public final boolean isIgnored() { return m_ReadGuard.evaluate( () -> m_Ignore ); }
302
303    /**
304     *  Returns the flag that indicates whether reports should be sent if a
305     *  {@link PerformanceTracker}
306     *  is aborted.
307     *
308     *  @return {@true} if a report should be sent, {@false} if not.
309     *
310     *  @see PerformanceTracker#abort(String)
311     *  @see PerformanceTracker#abort(String, Throwable)
312     */
313    public final boolean isSendingReportForAbort() { return m_ReadGuard.evaluate( () -> m_SendReportForAbort ); }
314
315    /**
316     *  Returns the flag that indicates whether reports should be sent only if
317     *  the threshold was exceeded.
318     *
319     *  @return {@true} if a report should be sent only when the threshold
320     *      was exceeded, {@false} if a report should be sent always.
321     */
322    @SuppressWarnings( "NewMethodNamingConvention" )
323    public final boolean isSendingReportOnlyForExceededThreshold() { return m_ReadGuard.evaluate( () -> m_SendReportOnlyForExceededThreshold ); }
324
325    /**
326     *  Sets the flag that controls whether this performance section is
327     *  ignored.
328     *
329     *  @param  flag    {@true} if the performance section should be
330     *      ignored from now on, {@false} if it has to be observed in
331     *      future.
332     */
333    public final void setIgnoreFlag( final boolean flag ) { m_WriteGuard.perform( () -> m_Ignore = flag ); }
334
335    /**
336     *  Sets the flag that controls whether reports are sent also for aborted
337     *  {@linkplain PerformanceTracker performance trackers}.
338     *
339     *  @param  flag {@true} if reports should be sent, {@false} if
340     *      not.
341     */
342    public final void setSendReportForAbortFlag( final boolean flag )
343    {
344        m_WriteGuard.perform( () -> m_SendReportForAbort = flag );
345    }   //  setSendReportForAbortFlag()
346
347    /**
348     *  Sets the flag that controls whether reports are sent only when the
349     *  threshold was exceeded.
350     *
351     *  @param  flag {@true} if reports should be sent only when the
352     *      threshold was exceeded, {@false} if reports should be sent
353     *      always.
354     */
355    @SuppressWarnings( "NewMethodNamingConvention" )
356    public final void setSendReportOnlyForExceededThresholdFlag( final boolean flag )
357    {
358        m_WriteGuard.perform( () -> m_SendReportOnlyForExceededThreshold = flag );
359    }   //  setSendReportOnlyForExceededThresholdFlag()
360
361    /**
362     *  Sets the threshold and enables it.
363     *
364     *  @param  value   The new threshold value; must be greater than 0.
365     */
366    public final void setThreshold( final TimeValue value )
367    {
368        try
369        {
370            m_WriteGuard.perform( () -> m_Threshold = validateThreshold( value ) );
371        }
372        catch( final ExecutionFailedException e )
373        {
374            final var cause = e.getCause();
375            if( cause instanceof final IllegalArgumentException iae ) throw iae;
376            throw e;
377        }
378    }   //  setThreshold()
379
380    /**
381     *  Sets the timeout and enables it.
382     *
383     *  @param  value   The new timeout value; must be greater than 0.
384     */
385    public final void setTimeout( final TimeValue value )
386    {
387        try
388        {
389            m_WriteGuard.perform( () -> m_Timeout = validateTimeout( value ) );
390        }
391        catch( final ExecutionFailedException e )
392        {
393            final var cause = e.getCause();
394            if( cause instanceof final IllegalArgumentException iae ) throw iae;
395            throw e;
396        }
397    }   //  setTimeout()
398
399    /**
400     *  Disables the threshold for this performance section.
401     */
402    public final void switchOffThreshold() { m_WriteGuard.perform( () -> m_Threshold = null ); }
403
404    /**
405     *  Disables the timeout for this performance section.
406     */
407    public final void switchOffTimeout() { m_WriteGuard.perform( () -> m_Timeout = null ); }
408
409    /**
410     * {@inheritDoc}
411     */
412    @Override
413    public final String toString()
414    {
415        final var buffer = new StringBuilder( "PerformanceSection{" )
416            .append( "m_Name='" ).append( m_Name ).append( "', " )
417            .append( "m_Description='" ).append( m_Description ).append( "', " )
418            .append( "m_Threshold=" );
419        try( final var _ = m_ReadGuard.lock() )
420        {
421            if( nonNull( m_Threshold ) )
422            {
423                buffer.append( "%s".formatted( m_Threshold ) );
424            }
425            else
426            {
427                buffer.append( "none" );
428            }
429            buffer.append( ", m_Timeout=" );
430            if( nonNull( m_Timeout ) )
431            {
432                buffer.append( "%s".formatted( m_Timeout ) );
433            }
434            else
435            {
436                buffer.append( "none" );
437            }
438            buffer.append( ", m_Ignore=" ).append( m_Ignore )
439                .append( ", m_SendReportForAbort=" ).append( m_SendReportForAbort )
440                .append( ", m_SendReportOnlyForExceededThreshold=" ).append( m_SendReportOnlyForExceededThreshold )
441                .append( '}' );
442        }
443        final var retValue = buffer.toString();
444
445        //---* Done *----------------------------------------------------------
446        return retValue;
447    }   //  toString()
448
449    /**
450     *  <p>{@summary Validates the given threshold.}</p>
451     *  <p>The value must be greater than 0, and, if the
452     *  timeout is not {@null}, it must be less than the timeout.</p>
453     *
454     *  @note The method is called from inside a guarded area only!
455     *
456     *  @param  value   The value for the threshold.
457     *  @return The given value.
458     *  @throws IllegalArgumentException The value is invalid.
459     */
460    private final TimeValue validateThreshold( final TimeValue value ) throws IllegalArgumentException
461    {
462        final var retValue = validateThresholdAndTimeout( requireNonNullArgument( value, "threshold" ), value, m_Timeout );
463
464        //---* Done *----------------------------------------------------------
465        return retValue;
466    }   //  validateThreshold()
467
468    /**
469     *  <p>{@summary Validates threshold and timeout.}</p>
470     *  <p>Both values must be either -1 or greater than 0, and, if the
471     *  threshold is not -1, the timeout must be greater than the
472     *  threshold.</p>
473     *
474     *  @param  retValue    The return value.
475     *  @param  threshold   The value for the threshold.
476     *  @param  timeout The value for the timeout.
477     *  @return The return value.
478     *  @throws IllegalArgumentException A value is invalid.
479     */
480    @SuppressWarnings( "OverlyComplexMethod" )
481    private static final TimeValue validateThresholdAndTimeout( final TimeValue retValue, final TimeValue threshold, final TimeValue timeout ) throws IllegalArgumentException
482    {
483        if( nonNull( retValue ) && !retValue.equals( threshold ) && !retValue.equals( timeout ) ) throw new IllegalArgumentException( "return value is neither threshold nor timeout" );
484        if( nonNull( threshold ) && threshold.value().longValue() < 1L ) throw new ValidationException( "threshold value '%s' is invalid".formatted( threshold ) );
485        if( nonNull( timeout ) && timeout.value().longValue() < 1L ) throw new ValidationException( "timeout value '%s' is invalid".formatted( timeout ) );
486        if( nonNull( timeout ) && nonNull( threshold ) && timeout.baseValue().compareTo( threshold.baseValue() ) <= 0 ) throw new ValidationException( "timeout value '%s' is not greater than threshold value '%s'".formatted( timeout, threshold ) );
487
488        //---* Done *----------------------------------------------------------
489        return retValue;
490    }   //  validateThresholdAndTimeout()
491
492    /**
493     *  <p>{@summary Validates the given timeout.}</p>
494     *  <p>The value must be greater than 0, and, if the threshold is not
495     *  {@null}, the timeout must be greater than the threshold.</p>
496     *
497     *  @note The method is called from inside a guarded area only!
498     *
499     *  @param  value   The value for the timeout.
500     *  @return The given value.
501     *  @throws IllegalArgumentException The value is invalid.
502     */
503    private final TimeValue validateTimeout( final TimeValue value ) throws IllegalArgumentException
504    {
505        final var retValue = validateThresholdAndTimeout( requireNonNullArgument( value, "timeout" ), m_Threshold, value );
506
507        //---* Done *----------------------------------------------------------
508        return retValue;
509    }   //  validateThresholdAndTimeout()
510}
511//  class PerformanceSection
512
513/*
514 *  End of File
515 */