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.exception;
019
020import static java.util.Arrays.stream;
021import static org.apiguardian.api.API.Status.STABLE;
022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
023
024import java.io.Serial;
025import java.util.Collection;
026import java.util.stream.Stream;
027
028import org.apiguardian.api.API;
029import org.tquadrat.foundation.annotation.ClassVersion;
030import org.tquadrat.foundation.function.Functions;
031
032/**
033 *  <p>{@summary A &quot;container&quot; exception for exceptions thrown within
034 *  lambda expressions.}</p>
035 *  <p>When a method that emits a checked exception is called inside a lambda
036 *  function, this has to be caught <i>inside</i> that function; it is not
037 *  possible to declare that exception for the method, as long as the
038 *  underlying
039 *  {@linkplain FunctionalInterface functional interface}
040 *  have not done that already.</p>
041 *  <p>Unfortunately, the methods from the interfaces in the package
042 *  {@link java.util.function}
043 *  do not declare any exception, for good reason. So the code below is not
044 *  possible:</p>
045 *  <div class="source-container"><pre>&hellip;
046 *  Appendable appendable = &hellip;
047 *  Consumer appender = s -&gt; appendable.append( s );
048 *  appender.accept( "&hellip;" );
049 *  &hellip;</pre></div>
050 *  <p>because
051 *  {@link Appendable#append(CharSequence)}
052 *  declares to throw an
053 *  {@link java.io.IOException}.</p>
054 *  <p>This class now is meant to wrap those exceptions and to allow them to
055 *  bubble up to the caller:</p>
056 *  <div class="source-container"><pre>&hellip;
057 *  Appendable appendable = &hellip;
058 *  Consumer appender =
059 *  {
060 *    try
061 *    {
062 *      s -&gt; appendable.append( s );
063 *    }
064 *    catch( IOException e )
065 *    {
066 *      throw new LambdaContainerException( e );
067 *    }
068 *  }
069 *
070 *  try
071 *  {
072 *    appender.accept( "&hellip;" );
073 *  }
074 *  catch( LambdaContainerException e )
075 *  {
076 *    throw (IOException) e.getCause();
077 *  }
078 *  &hellip;</pre></div>
079 *  <p>When said above that the methods from the functional interfaces in the
080 *  {@code java.util.function} <i>unfortunately</i> do not declare any
081 *  exception, this was only focused on their use with code that may emit
082 *  checked exceptions. But in fact it is a good thing that the methods in
083 *  these interfaces do not declare any exceptions, as this would have polluted
084 *  all the APIs that make use of these functional interfaces. And using the
085 *  pattern above would be an alternative. Another would be the methods
086 *  provided in the class
087 *  {@link Functions}.</p>
088 *
089 *  @author Thomas Thrien - thomas.thrien@tquadrat.org
090 *  @version $Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $
091 *  @since 0.1.0
092 *
093 *  @UMLGraph.link
094 */
095@ClassVersion( sourceVersion = "$Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $" )
096@API( status = STABLE, since = "0.1.0" )
097public final class LambdaContainerException extends CheckedExceptionWrapper
098{
099        /*---------------*\
100    ====** Inner Classes **====================================================
101        \*---------------*/
102    /**
103     *  <p>{@summary The exception that is thrown when the container holds an unexpected
104     *  exception.}</p>
105     *
106     *  @author Thomas Thrien - thomas.thrien@tquadrat.org
107     *  @version $Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $
108     *  @since 0.1.0
109     *
110     *  @UMLGraph.link
111     */
112    private final class UnexpectedException extends RuntimeException
113    {
114            /*------------------------*\
115        ====** Static Initialisations **=======================================
116            \*------------------------*/
117        /**
118         *  The serial version UID for objects of this class: {@value}.
119         */
120        @Serial
121        private static final long serialVersionUID = 1L;
122
123            /*--------------*\
124        ====** Constructors **=================================================
125            \*--------------*/
126        /**
127         *  <p>{@summary {@constructor}}</p>
128         */
129        public UnexpectedException()
130        {
131            super( "The Exception '%s' was not expected".formatted( LambdaContainerException.this.getCause().getClass().getName() ) );
132            setStackTrace( LambdaContainerException.this.getCause().getStackTrace() );
133        }   //  UnexpectedException()
134
135            /*---------*\
136        ====** Methods **======================================================
137            \*---------*/
138        /**
139         *  {@inheritDoc}
140         *
141         *  @see Throwable#getCause()
142         */
143        @Override
144        public final synchronized Throwable getCause() { return LambdaContainerException.this.getCause(); }
145    }
146    //  class UnexpectedException
147
148        /*------------------------*\
149    ====** Static Initialisations **===========================================
150        \*------------------------*/
151    /**
152     *  The serial version UID for objects of this class: {@value}.
153     *
154     *  @hidden
155     */
156    @Serial
157    private static final long serialVersionUID = 1L;
158
159        /*--------------*\
160    ====** Constructors **=====================================================
161        \*--------------*/
162    /**
163     *  <p>{@summary Creates a new {@class} instance for the given
164     *  exception.}</p>
165     *
166     *  @param  e   The exception to wrap; <i>cannot</i> be {@null}.
167     */
168    public LambdaContainerException( final Exception e )
169    {
170        super( requireNonNullArgument( e, "e" ) );
171    }   //  LambdaContainerException()
172
173        /*---------*\
174    ====** Methods **==========================================================
175        \*---------*/
176    /**
177     *  <p>{@summary Checks whether the contained Exception is somehow
178     *  expected.}</p>
179     *
180     *  @param  expected    The expected exceptions.
181     *  @return {@true} if the contained Exception is among the list of
182     *      expected exceptions, {@false} otherwise.
183     */
184    public final boolean checkIfExpected( final Stream<Class<? extends Exception>> expected )
185    {
186        final var cause = getCause();
187        final var retValue = requireNonNullArgument( expected, "expected" ).anyMatch( e -> e.isInstance( cause ) );
188
189        //---* Done *----------------------------------------------------------
190        return retValue;
191    }   //  checkIfExpected()
192
193    /**
194     *  <p>{@summary Checks whether the contained Exception is somehow
195     *  expected.}</p>
196     *
197     *  @param  expected    The expected exceptions.
198     *  @return {@true} if the contained Exception is among the list of
199     *      expected exceptions, {@false} otherwise.
200     */
201    public final boolean checkIfExpected( final Collection<Class<? extends Exception>> expected )
202    {
203        final var retValue = checkIfExpected( requireNonNullArgument( expected, "expected" ).stream() );
204
205        //---* Done *----------------------------------------------------------
206        return retValue;
207    }   //  checkIfExpected()
208
209    /**
210     *  <p>{@summary Checks whether the contained Exception is somehow
211     *  expected.}</p>
212     *
213     *  @param  expected    The expected exceptions.
214     *  @return {@true} if the contained Exception is among the list of
215     *      expected exceptions, {@false} otherwise.
216     */
217    @SafeVarargs
218    public final boolean checkIfExpected( final Class<? extends Exception>... expected )
219    {
220        final var retValue = checkIfExpected( stream( requireNonNullArgument( expected, "expected" ) ) );
221
222        //---* Done *----------------------------------------------------------
223        return retValue;
224    }   //  checkIfExpected()
225
226    /**
227     *  <p>{@summary Returns the contained Exception if it is of the given
228     *  type; otherwise a
229     *  {@link RuntimeException}
230     *  is thrown.}</p>
231     *
232     *  @param  <T> The expected Exception type.
233     *  @param  exceptionType   The expected type.
234     *  @return The contained Exception.
235     *  @throws RuntimeException    This is in fact an
236     *      {@link UnexpectedException} that is thrown when the contained
237     *      Exception was not expected.
238     */
239    @SuppressWarnings( {"ProhibitedExceptionDeclared", "OverlyBroadThrowsClause"} )
240    public final <T> T getCheckedCause( final Class<T> exceptionType ) throws RuntimeException
241    {
242        final var cause = getCause();
243        if( !requireNonNullArgument( exceptionType, "exceptionType" ).isInstance( cause ) )
244        {
245            throw new UnexpectedException();
246        }
247
248        final var retValue = exceptionType.cast( cause );
249
250        //---* Done *----------------------------------------------------------
251        return retValue;
252    }   //  getCheckedCause()
253}
254//  class LambdaContainerException
255
256/*
257 *  End of File
258 */