Skip to content

Commit

Permalink
ont-api: add BufferedHeadInputStream, change OntGraphUtils#readGraph …
Browse files Browse the repository at this point in the history
…to ignore OWLXML (workaround for apache/jena#2620)
  • Loading branch information
sszuev committed Aug 9, 2024
1 parent f1ecf02 commit aefcaba
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 8 deletions.
104 changes: 104 additions & 0 deletions src/main/java/com/github/owlcs/ontapi/BufferedHeadInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.github.owlcs.ontapi;

import javax.annotation.Nonnull;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Objects;

/**
* An InputStream with cached head.
*/
public class BufferedHeadInputStream extends InputStream {
private final InputStream source;
private final byte[] head;
private final int headEnd;
private int headPos = 0;
private boolean closed;

public BufferedHeadInputStream(InputStream source, int cacheSize) {
this.source = Objects.requireNonNull(source);
this.head = new byte[cacheSize];
try {
headEnd = source.read(head, 0, head.length);
} catch (IOException e) {
throw new UncheckedIOException("Failed to fill cache", e);
}
}

public byte[] head() {
return head;
}

private void checkOpen() throws IOException {
if (closed) {
throw new IOException("Stream is already closed");
}
}

@Override
public void close() throws IOException {
source.close();
closed = true;
}

@Override
public int read() throws IOException {
checkOpen();
if (headPos < headEnd) {
return head[headPos++] & 0xFF;
} else {
return source.read();
}
}

@Override
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
checkOpen();
if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}

int bytesRead = 0;
if (headPos < headEnd) {
int bytesToReadFromCache = Math.min(len, headEnd - headPos);
System.arraycopy(head, headPos, b, off, bytesToReadFromCache);
headPos += bytesToReadFromCache;
bytesRead += bytesToReadFromCache;
off += bytesToReadFromCache;
len -= bytesToReadFromCache;
}

if (len > 0) {
int bytesToReadFromSource = source.read(b, off, len);
if (bytesToReadFromSource > 0) {
bytesRead += bytesToReadFromSource;
}
}

return bytesRead == 0 ? -1 : bytesRead;
}

@Override
public int available() throws IOException {
checkOpen();
return (headEnd - headPos) + source.available();
}

@Override
public long skip(long n) throws IOException {
checkOpen();
long skipped = 0;
if (headPos < headEnd) {
int bytesToSkipInCache = (int) Math.min(n, headEnd - headPos);
headPos += bytesToSkipInCache;
skipped += bytesToSkipInCache;
n -= bytesToSkipInCache;
}
if (n > 0) {
skipped += source.skip(n);
}
return skipped;
}
}
24 changes: 16 additions & 8 deletions src/main/java/com/github/owlcs/ontapi/OntGraphUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
Expand Down Expand Up @@ -366,7 +365,7 @@ public static OntFormat readGraph(Graph graph,
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("read {}, try <{}>", iri, lang);
}
readGraph(graph, stream, iri.toString(), lang);
RDFDataMgr.read(graph, getInputStream(format, stream), iri.toString(), lang);
return format;
} catch (OWLOntologyInputSourceException | IOException e) {
throw new OWLOntologyCreationException(source.getClass().getSimpleName() +
Expand All @@ -384,14 +383,23 @@ public static OntFormat readGraph(Graph graph,
throw error;
}

protected static void readGraph(Graph graph, Closeable stream, String base, Lang lang) {
if (stream instanceof InputStream) {
RDFDataMgr.read(graph, (InputStream) stream, base, lang);
} else if (stream instanceof StringReader) {
RDFDataMgr.read(graph, (StringReader) stream, base, lang);
private static InputStream getInputStream(OntFormat format, Closeable stream) {
InputStream res;
if (stream instanceof Reader) {
res = new ReaderInputStream((Reader) stream, StandardCharsets.UTF_8);
} else {
RDFDataMgr.read(graph, new ReaderInputStream((Reader) stream, StandardCharsets.UTF_8), base, lang);
res = (InputStream) stream;
}
if (format == OntFormat.RDF_XML) {
// check if it is OWLXML
BufferedHeadInputStream is = new BufferedHeadInputStream(res, 8192);
String head = new String(is.head(), StandardCharsets.UTF_8);
if (head.contains("<Ontology ") && head.contains("<Prefix name=")) {
throw new JenaException("OWL/XML is not supported");
}
res = is;
}
return res;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package com.github.owlcs.ontapi.tests;

import com.github.owlcs.ontapi.BufferedHeadInputStream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class BufferedHeadInputStreamTest {

@Test
void testRead() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);

int firstByte = rewindableStream.read();
Assertions.assertEquals('T', firstByte);

byte[] buffer = new byte[9];
int bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(9, bytesRead);
Assertions.assertArrayEquals("his is a ".getBytes(StandardCharsets.UTF_8), buffer);

// Read more bytes
bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(9, bytesRead);
Assertions.assertArrayEquals("test inpu".getBytes(StandardCharsets.UTF_8), buffer);

// Read remaining bytes
bytesRead = rewindableStream.read(buffer, 0, 8);
Assertions.assertEquals(8, bytesRead);
Assertions.assertArrayEquals("t stream".getBytes(StandardCharsets.UTF_8),
new byte[]{buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]});
}

@Test
void testReadWithOffsetGreaterThanBufferLength() {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);
byte[] buffer = new byte[10];
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rewindableStream.read(buffer, 11, 1));
}

@Test
void testReadWithLengthGreaterThanBufferLength() {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);
byte[] buffer = new byte[10];
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rewindableStream.read(buffer, 0, 11));
}

@Test
void testReadWithOffsetPlusLengthGreaterThanBufferLength() {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);
byte[] buffer = new byte[10];
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rewindableStream.read(buffer, 6, 5));
}

@Test
void testReadWhenEndOfStream() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);
byte[] buffer = new byte[testData.length()];
int bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(testData.length(), bytesRead);

bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(-1, bytesRead);
}

@Test
void testReadWithLargeBuffer() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 5);
byte[] buffer = new byte[50];
int bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(testData.length(), bytesRead);
Assertions.assertArrayEquals(testData.getBytes(StandardCharsets.UTF_8), Arrays.copyOfRange(buffer, 0, bytesRead));
}

@Test
void testReadWithSmallBuffer() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 20);
byte[] buffer = new byte[4];
int bytesRead1 = rewindableStream.read(buffer);
Assertions.assertEquals(4, bytesRead1);
Assertions.assertArrayEquals("This".getBytes(StandardCharsets.UTF_8), buffer);
int bytesRead2 = rewindableStream.read(buffer);
Assertions.assertEquals(4, bytesRead2);
Assertions.assertArrayEquals(" is ".getBytes(StandardCharsets.UTF_8), buffer);
}

@Test
void testReadWithExactCacheSize() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, testData.length());
byte[] buffer = new byte[testData.length()];
int bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(testData.length(), bytesRead);
Assertions.assertArrayEquals(testData.getBytes(StandardCharsets.UTF_8), buffer);
}

@Test
void testReadWithCacheSmallerThanData() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 5);
byte[] buffer = new byte[10];
int bytesRead1 = rewindableStream.read(buffer);
Assertions.assertEquals(10, bytesRead1);
Assertions.assertArrayEquals("This is a ".getBytes(StandardCharsets.UTF_8), buffer);
int bytesRead2 = rewindableStream.read(buffer);
Assertions.assertEquals(10, bytesRead2);
Assertions.assertArrayEquals("test input".getBytes(StandardCharsets.UTF_8), buffer);
}

@Test
void testReadWithCacheLargerThanData() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 50);
byte[] buffer = new byte[testData.length()];
int bytesRead = rewindableStream.read(buffer);
Assertions.assertEquals(testData.length(), bytesRead);
Assertions.assertArrayEquals(testData.getBytes(StandardCharsets.UTF_8), buffer);
}

@Test
void testAvailable() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);

int available = rewindableStream.available();
Assertions.assertEquals(testData.length(), available);

byte[] buffer = new byte[10];
Assertions.assertEquals(10, rewindableStream.read(buffer));
available = rewindableStream.available();
Assertions.assertEquals(testData.length() - 10, available);
}

@Test
void testSkip() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);

long skipped = rewindableStream.skip(5);
Assertions.assertEquals(5, skipped);

int nextByte = rewindableStream.read();
Assertions.assertEquals('i', nextByte);

skipped = rewindableStream.skip(testData.length());
Assertions.assertEquals(testData.length() - 6, skipped);

nextByte = rewindableStream.read();
Assertions.assertEquals(-1, nextByte); // End of stream
}

@Test
void testClose() throws IOException {
String testData = "This is a test input stream";
InputStream originalStream = new ByteArrayInputStream(testData.getBytes(StandardCharsets.UTF_8));
BufferedHeadInputStream rewindableStream = new BufferedHeadInputStream(originalStream, 10);
rewindableStream.close();
Assertions.assertThrows(IOException.class, rewindableStream::read);
}
}

0 comments on commit aefcaba

Please sign in to comment.