Как определить метод для чтения всех InputStreams, включая ZipInputStream?
Я спрашивал об этом однажды, и мой пост был удален за то, что он не предоставил код, который использует вспомогательный класс. На этот раз я создал полный набор тестов, который показывает точную проблему.
Я придерживаюсь мнения, что Java ZipInputStream нарушает принцип подстановки Лискова (LSP) в отношении абстрактного класса InputStream. Если ZipInputStream является подтипом InputStream, тогда объекты типа InputStream в программе могут быть заменены объектами типа ZipInputStream без изменения каких-либо желательных свойств этой программы (корректность, выполненная задача и т. Д.).
Здесь нарушается LSP для методов чтения.
InputStream.read (byte [], int, int) сообщает, что возвращает:
общее количество байтов, считанных в буфер, или -1, если данных больше нет, потому что достигнут конец потока.
Проблема с ZipInputStream заключается в том, что он изменил значение возвращаемого значения -1. Говорится:
фактическое число прочитанных байтов или -1, если достигнут конец записи
(на самом деле есть подсказка для аналогичной проблемы с доступным методом в документации Android http://developer.android.com/reference/java/util/zip/ZipInputStream.html)
Теперь код, который демонстрирует проблему. (Это урезанная версия того, что я на самом деле пытался сделать, поэтому прошу прощения за любой плохой стиль, проблемы с многопоточностью или тот факт, что поток расширен и т. Д.).
Класс, который принимает любой InputStream для генерации SHA1 потока:
public class StreamChecker {
private byte[] lastHash = null;
public boolean isDifferent(final InputStream inputStream) throws IOException {
final byte[] hash = generateHash(inputStream);
final byte[] temp = lastHash;
lastHash = hash;
return !Arrays.equals(temp, hash);
}
private byte[] generateHash(final InputStream inputStream) throws IOException {
return DigestUtils.sha1(inputStream);
}
}
Модульные тесты:
public class StreamCheckerTest {
@Test
public void testByteArrayInputStreamIsSame() throws IOException {
final StreamChecker checker = new StreamChecker();
final byte[] bytes = "abcdef".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertFalse(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testByteArrayInputStreamWithDifferentDataIsDifferent() throws IOException {
final StreamChecker checker = new StreamChecker();
byte[] bytes = "abcdef".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
bytes = "123456".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testZipInputStreamIsSame() throws IOException {
final StreamChecker checker = new StreamChecker();
final byte[] bytes = "abcdef".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertFalse(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testZipInputStreamWithDifferentEntryDataIsDifferent() throws IOException {
final StreamChecker checker = new StreamChecker();
byte[] bytes = "abcdef".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
bytes = "123456".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
// Fails here
Assert.assertTrue(checker.isDifferent(stream));
}
}
private ZipInputStream createZipStream(final String entryName,
final byte[] bytes) throws IOException {
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final ZipOutputStream stream = new ZipOutputStream(outputStream)) {
stream.putNextEntry(new ZipEntry(entryName));
stream.write(bytes);
return new ZipInputStream(new ByteArrayInputStream(
outputStream.toByteArray()));
}
}
}
Итак, вернемся к проблеме... LSP нарушается, поскольку вы можете читать до конца потока для InputStream, но не для ZipInputStream, и, конечно, это нарушит свойство корректности любого метода, который пытается использовать его таким образом,
Есть ли способ, которым это может быть достигнуто, или ZipInputStream в корне ошибочен?
1 ответ
Я не вижу нарушения LSP. Документация для ZipInputStream.read(byte[], int, int)
говорит "Читает из текущей записи ZIP в массив байтов".
В любое время ZipInputStream
действительно входной поток записи, а не весь ZIP-файл. И трудно понять, что еще ZipInputStream.read()
может сделать в конце записи, кроме возврата -1.
это нарушит свойство корректности любого метода, который пытается использовать его таким образом
Трудно понять, как этот метод узнает.