Как проверить, что ImageInputStream уже закрыт? - PullRequest
1 голос
/ 14 апреля 2019

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

javax.imageio.stream.ImageInputStream # метод close выдает исключение когда поток уже закрыт.

Я знаю о ((MemoryCacheImageInputStream) ios).isClosed() трюке. Но этот метод имеет частный уровень доступа и обеспечивает применение мерзкого приведения.

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

try {
    imageInputStream.close();
} catch (IOException onClose) {
    String message = onClose.getMessage();
    if ("closed".equals(message)) {
        // suppress the exception and write to log
    } else {
        throw new IllegalStateException(onClose);
    }
}

Есть ли элегантный способ проверить состояние ImageInputStream?

Ответы [ 2 ]

1 голос
/ 15 апреля 2019

На самом деле вам не нужно проверять состояние потока, вам нужно только убедиться, что он не закрыт более одного раза. Один из вариантов - обернуть ImageInputStream в другой класс, который переопределяет close(), чтобы быть недоступным в случае, если поток уже закрыт. Хорошая вещь об этом, это будет хорошо работать с try-with-resources , вот так:

try (ImageInputStream stream = new CloseableStreamFix(ImageIO.createImageInputStream(input))) {
    stream.close(); // Close stream once (or as many times you want)
}
// stream implicitly closed again by automatic resource handling, no exception

К сожалению, код для CloseableStreamFix нетривиален, поэтому я не уверен, считается ли он "элегантным" (хотя использование используется):

final class CloseableStreamFix extends ImageInputStreamImpl {

    private boolean closed;
    private final ImageInputStream delegate;

    public CloseableStreamFix(ImageInputStream delegate) {
        this.delegate = delegate;
    }

    // The method you actually want to override.
    @Override
    public void close() throws IOException {
        if (!closed) {
            closed = true;

            super.close();
            delegate.close();
        }
    }

    // You have to implement these abstract read methods. Easy, just delegate them.
    // ...except you need to keep the stream position in sync.
    @Override
    public int read() throws IOException {
        streamPos++;
        return delegate.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int read = delegate.read(b, off, len);

        if (read > 0) {
            streamPos += read;
        }

        return read;
    }

    // In a perfect world, the above should be all you need to do. Unfortunately, it's not.


    // We need to keep the delegate position in sync with the position in this class.
    // Overriding the seek method should do.
    @Override
    public void seek(long pos) throws IOException {
        super.seek(pos); // Don't forget to call super here, as we rely on positions being in sync.
        delegate.seek(pos);
    }

    // Some plugins require stream length, so we need to delegate that.
    @Override
    public long length() {
        try {
            // Unfortunately, this method does not declare IOException like the
            // interface method does, so we need this strange try/catch here.
            return delegate.length();
        } catch (IOException e) {
            // It's also possible to use a generics hack to throw a checked
            // exception as unchecked. I leave that as an exercise...
            throw new UndeclaredThrowableException(e);
        }
    }

    // You may be able to skip the flush methods. If you do, skip both.
    @Override
    public void flushBefore(long pos) throws IOException {
        delegate.flushBefore(pos);
    }

    @Override
    public long getFlushedPosition() {
        return delegate.getFlushedPosition();
    }

    // You could probably skip the methods below, as I don't think they are ever used as intended.
    @Override
    public boolean isCached() {
        return delegate.isCached();
    }

    @Override
    public boolean isCachedMemory() {
        return delegate.isCachedMemory();
    }

    @Override
    public boolean isCachedFile() {
        return delegate.isCachedFile();
    }
}

... и, хотя я думаю, вышеизложенное охватывает все базы, вам, вероятно, стоит проверить это.

Если вы не планируете использовать много операторов try-with-resources, вы, вероятно, найдете простую try / catch (как у вас уже есть) более удобочитаемой. Я хотел бы извлечь его как метод, как это, хотя:

static void close(Closeable closeable) throws IOException {
    try {
        closeable.close();
    }
    catch (IOException e) {
        if (!"closed".equals(e.getMessage())) {
            throw e;
        }
        // Otherwise, we're already closed, just ignore it,
    }
}

Просто знайте, что опора на сообщение об исключении, подобное этому , может сломаться в будущих выпусках Java, если кто-то решит, что требуется лучшее объяснение ...

1 голос
/ 14 апреля 2019

Один из способов сделать это - создать класс, расширяющий ImageInputStream, и реализовать собственный метод isClosed(), например, переопределив метод close(), чтобы установить логический флаг в значение true при закрытии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...