Я спрашивал об этом однажды, и мой пост был удален за то, что он не предоставил код, использующий вспомогательный класс. На этот раз я создал полный набор тестов, который показывает точную проблему.
Я считаю, что ZipInputStream в Java нарушает принцип подстановки Лискова (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 в корне ошибочен?