OrekitException.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.errors;

import java.text.MessageFormat;
import java.util.Locale;

import org.hipparchus.exception.Localizable;
import org.hipparchus.exception.MathRuntimeException;

/** This class is the base class for all specific exceptions thrown by
 * the Orekit classes.

 * <p>When the Orekit classes throw exceptions that are specific to
 * the package, these exceptions are always subclasses of
 * OrekitException. When exceptions that are already covered by the
 * standard java API should be thrown, like
 * ArrayIndexOutOfBoundsException or InvalidParameterException, these
 * standard exceptions are thrown rather than the Hipparchus specific
 * ones.</p>
 * <p>This class also provides utility methods to throw some standard
 * java exceptions with localized messages.</p>
 *
 * @author Luc Maisonobe

 */

public class OrekitException extends RuntimeException implements LocalizedException {

    /** Serializable UID. */
    private static final long serialVersionUID = 20150611L;

    /** Format specifier (to be translated). */
    private final Localizable specifier;

    /** Parts to insert in the format (no translation). */
    private final Object[] parts;

    /** Simple constructor.
     * Build an exception with a translated and formatted message
     * @param specifier format specifier (to be translated)
     * @param parts parts to insert in the format (no translation)
     */
    public OrekitException(final Localizable specifier, final Object... parts) {
        this.specifier = specifier;
        this.parts     = (parts == null) ? new Object[0] : parts.clone();
    }

    /** Copy constructor.
     * @param exception exception to copy from
     * @since 5.1
     */
    public OrekitException(final OrekitException exception) {
        super(exception);
        this.specifier = exception.specifier;
        this.parts     = exception.parts.clone();
    }

    /** Simple constructor.
     * Build an exception from a cause and with a specified message
     * @param message descriptive message
     * @param cause underlying cause
     */
    public OrekitException(final Localizable message, final Throwable cause) {
        super(cause);
        this.specifier = message;
        this.parts     = new Object[0];
    }

    /** Simple constructor.
     * Build an exception from a cause and with a translated and formatted message
     * @param cause underlying cause
     * @param specifier format specifier (to be translated)
     * @param parts parts to insert in the format (no translation)
     */
    public OrekitException(final Throwable cause, final Localizable specifier,
                           final Object... parts) {
        super(cause);
        this.specifier = specifier;
        this.parts     = (parts == null) ? new Object[0] : parts.clone();
    }

    /** Simple constructor.
     * Build an exception from an Hipparchus exception
     * @param exception underlying Hipparchus exception
     * @since 6.0
     */
    public OrekitException(final MathRuntimeException exception) {
        super(exception);
        this.specifier = exception.getSpecifier();
        this.parts     = exception.getParts();
    }

    /** {@inheritDoc} */
    @Override
    public String getMessage(final Locale locale) {
        return buildMessage(locale);
    }

    /** {@inheritDoc} */
    @Override
    public String getMessage() {
        return getMessage(Locale.US);
    }

    /** {@inheritDoc} */
    @Override
    public String getLocalizedMessage() {
        return getMessage(Locale.getDefault());
    }

    /** {@inheritDoc} */
    @Override
    public Localizable getSpecifier() {
        return specifier;
    }

    /** {@inheritDoc} */
    @Override
    public Object[] getParts() {
        return parts.clone();
    }

    /** Recover a OrekitException, possibly embedded in a {@link MathRuntimeException}.
     * <p>
     * If the {@code MathRuntimeException} does not embed a OrekitException, a
     * new one will be created.
     * </p>
     * @param exception MathRuntimeException to analyze
     * @return a (possibly embedded) OrekitException
     */
    public static OrekitException unwrap(final MathRuntimeException exception) {

        for (Throwable t = exception; t != null; t = t.getCause()) {
            if (t instanceof OrekitException) {
                return (OrekitException) t;
            }
        }

        return new OrekitException(exception);

    }

    /**
     * Builds a message string by from a pattern and its arguments.
     * @param locale Locale in which the message should be translated
     * @return a message string
     */
    private String buildMessage(final Locale locale) {
        if (specifier == null) {
            return "";
        } else {
            try {
                final String localizedString = specifier.getLocalizedString(locale);
                if (localizedString == null) {
                    return "";
                } else {
                    return new MessageFormat(localizedString, locale).format(parts);
                }
                //CHECKSTYLE: stop IllegalCatch check
            } catch (Throwable t) {
                //CHECKSTYLE: resume IllegalCatch check
                // Message formatting or localization failed
                // Catch all exceptions to prevent the stack trace from being lost
                // Add the exception as suppressed so the user can fix that bug too
                this.addSuppressed(t);
                // just use the source string as the message
                return specifier.getSourceString();
            }
        }
    }

}