Совокупные исключения

Я часто нахожу необходимость проверки набора условий и вместо неудачного раннего (возвращающ false или бросаю исключение, когда первое условие не выполняется), мне нужно агрегировать результаты и сообщать о отдельных сбоях.

В настоящее время я использую список с пользовательскими записями (в основном запись состоит из типа сбоя и некоторого информационного сообщения) или какого-то наблюдателя (который также просто объединяет сбои), но у меня возникает ощущение, что это должно быть общей проблемой и что для ее решения должна быть какая-то существующая модель.

Ответ 1

Да, это обычная проблема, и оба подхода хороши.

javax.validation.Validator, который является стандартом для проверки Java, использует первое. Он возвращает Set of ConstraintViolations s

Если это соответствует вашему делу, я бы рекомендовал вместо javax.validation использовать javax.validation. Это спецификация с несколькими провайдерами, одна из которых hibernate-validator (нет необходимости использовать спящий режим для использования проекта проверки)

Ответ 2

Я не думаю, что вам нужно комплексное решение. Когда я должен это сделать, я обычно пишу что-то вроде:

List<String> errors=new ArrayList<String>();
...
if (foo<0)
  errors.add("Bad foo");
if (!bar.contains(plugh))
  errors.add("No plugh in bar");
... etc, whatever other errors ...
... then at the bottom ...
if (errors.size()>0)
{
  ... throw exception, display errors, whatever ...
}
... else celebrate and get on with it ...

Или, если я знаю, что все, что я собираюсь делать с ошибками, отображает одно большое сообщение, я могу просто сделать поле ошибки строкой и продолжать добавлять к нему сообщения в любом формате.

Ответ 3

Я использую следующий класс для сбора и отображения нескольких исключений. Он использует только стандартную java.

package util;

import java.io.ByteArrayOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;

/**
 * This abstract class is to be used for Exception generating by a collection of causes.
 * <p />
 * Typically: several tries take place to do something in different ways and each one fails. We therefore
 * have to collect the exceptions to document why it was not possible at all to do the thing.
 */
public abstract class AggregateException extends Exception
{
    /** A generator of random numbers */
    private final static Random rand = new Random();

    /** The causes of the exception */
    private final Vector<Throwable> causes;

    /** A (reasonably unique) id for this exception. Used for a better output of the stacktraces */
    private final long id = rand.nextLong();

    /**
     * @see Exception#Exception(String)
     * @param message
     */
    public AggregateException(String message, Collection<? extends Throwable> causes)
    {
        super(message);
        this.causes = new Vector<Throwable>(causes);
    }

    /**
     * Prints this throwable and its backtrace to the specified print stream.
     *
     * @param s <code>PrintStream</code> to use for output
     */
    public void printStackTrace(PrintStream s) {
        synchronized (s) {
            s.println(this);
            StackTraceElement[] trace = getStackTrace();
            for (int i=0; i < trace.length; i++)
                s.println("\tat " + trace[i]);

            final Throwable ourCause = getCause();
            if (ourCause != null)
                throw new AssertionError("The cause of an AggregateException should be null");

            for (int i = 0; i<causes.size(); i++)
            {
                final Throwable cause = causes.get(i);
                s.println(String.format(
                        "Cause number %s for AggregateException %s: %s ",
                        i,
                        getId(),
                        cause.toString()
                ));

                final ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream();
                final PrintStream ps = new PrintStream(byteArrayOS);
                cause.printStackTrace(ps);
                ps.close();
                final String causeStackTrace = byteArrayOS.toString();
                int firstCR = causeStackTrace.indexOf("\n");

                s.append(causeStackTrace.substring(firstCR == -1 ? 0 : firstCR+1));
            }
        }
    }

    @Override
    public String toString()
    {
        return String.format(
                "%s. AggregateException %s with %s causes.",
                super.toString(),
                getId(),
                causes.size()
                        );
    }

    @Override
    public Throwable initCause(Throwable cause)
    {
        if (cause != null)
            throw new AssertionError("The cause of an AggregateException must be null");

        return null;
    }

    /**
     *
     * @return {@link #id}
     */
    private String getId ()
    {
        return String.format("%xs", id);
    }

    /**
     * Test class
     */
    public static class TestException extends AggregateException
    {
        /**
         * Constructor
         * @param message
         * @param causes
         */
        public TestException(String message, Collection<? extends Throwable> causes)
        {
            super(message, causes);
        }

        /**
         * Test program
         *
         * @param notused
         * @throws AggregateException
         */
        public static void main (final String[] notused) throws AggregateException
        {
            final List<Error> causes = new LinkedList<Error> ();
            causes.add(new OutOfMemoryError());
            try
            {
                generateIOError();
            }
            catch (final Error th)
            {
                causes.add(th);
            }

            final AggregateException ae = new TestException("No test has sucessed", causes);

            throw ae;
        }

        /**
         * For test: generate an IOError caused by an IOException
         */
        private static void generateIOError()
        {
            try
            {
                generateIOException();
            }
            catch (final IOException ioex)
            {
                throw new IOError(ioex);
            }
        }

        /**
         * For test: throws an IOException
         * @throws IOException
         */
        private static void generateIOException() throws IOException
        {
            throw new IOException("xxx");
        }
    }


}