Skip to content

Commit

Permalink
Resource and Resource Loader API
Browse files Browse the repository at this point in the history
  • Loading branch information
dmlloyd committed Jun 28, 2024
1 parent d38bba8 commit 8c0cd42
Show file tree
Hide file tree
Showing 21 changed files with 1,828 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<module>net</module>
<module>os</module>
<module>ref</module>
<module>resource</module>
<module>version</module>
<module>vertx-context</module>
</modules>
Expand Down
Empty file added resource/build-release-17
Empty file.
29 changes: 29 additions & 0 deletions resource/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-parent</artifactId>
<version>2.5.0-SNAPSHOT</version>
</parent>

<artifactId>smallrye-common-resource</artifactId>

<name>SmallRye Common: Resources</name>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>smallrye-common-constraint</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.smallrye.common.resource;

import java.nio.file.DirectoryStream;
import java.util.Collections;
import java.util.Iterator;

/**
* An empty directory stream.
*/
public final class EmptyDirectoryStream<T> implements DirectoryStream<T> {
private static final EmptyDirectoryStream<Object> INSTANCE = new EmptyDirectoryStream<>();

private EmptyDirectoryStream() {
}

@SuppressWarnings("unchecked")
public static <T> EmptyDirectoryStream<T> instance() {
return (EmptyDirectoryStream<T>) INSTANCE;
}

public Iterator<T> iterator() {
return Collections.emptyIterator();
}

public void close() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.smallrye.common.resource;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* A resource representing an entry in a JAR file.
*/
public final class JarFileResource extends Resource {

private final URL base;
private final JarFile jarFile;
private final JarEntry jarEntry;
private URL url;

JarFileResource(final URL base, final JarFile jarFile, final JarEntry jarEntry) {
super(jarEntry.getName());
this.base = base;
this.jarFile = jarFile;
this.jarEntry = jarEntry;
}

public URL url() {
URL url = this.url;
if (url == null) {
try {
// todo: Java 20+: URL.of(new URI("jar", null, base.toURI().toASCIIString() + "!/" + pathName()),
// new ResourceURLStreamHandler(this));
url = this.url = new URL(null,
new URI("jar", null, base.toURI().toASCIIString() + "!/" + pathName()).toASCIIString(),
new ResourceURLStreamHandler(this));
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
return url;
}

/**
* {@inheritDoc}
*
* @implNote This implementation does not recognize directories that do not have an explicit entry.
* This restriction may be lifted in future versions.
*/
public boolean isDirectory() {
return jarEntry.isDirectory();
}

public DirectoryStream<Resource> openDirectoryStream() throws IOException {
if (!isDirectory()) {
return super.openDirectoryStream();
}
return new DirectoryStream<Resource>() {
Enumeration<JarEntry> entries;

public Iterator<Resource> iterator() {
if (entries == null) {
entries = jarFile.entries();
return new Iterator<Resource>() {
Resource next;

public boolean hasNext() {
String ourName = jarEntry.getName();
while (next == null) {
if (!entries.hasMoreElements()) {
return false;
}
JarEntry e = entries.nextElement();
String name = e.getName();
int ourLen = ourName.length();
if (name.startsWith(ourName) && !name.equals(ourName)) {
int idx = name.indexOf('/', ourLen);
if (idx == -1 || name.length() == idx + 1) {
next = new JarFileResource(base, jarFile, e);
}
break;
}
}
return true;
}

public Resource next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Resource next = this.next;
this.next = null;
return next;
}
};
}
throw new IllegalStateException();
}

public void close() {
entries = Collections.emptyEnumeration();
}
};
}

public Instant modifiedTime() {
FileTime fileTime = jarEntry.getLastModifiedTime();
return fileTime == null ? null : fileTime.toInstant();
}

public InputStream openStream() throws IOException {
return jarFile.getInputStream(jarEntry);
}

public long size() {
return jarEntry.getSize();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package io.smallrye.common.resource;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* A resource loader which corresponds to a JAR file.
*/
public final class JarFileResourceLoader implements ResourceLoader {
private final URL base;
private final JarFile jarFile;
private Path tempFile;

/**
* Construct a new instance.
*
* @param jarPath the path of the JAR file (must not be {@code null})
* @throws IOException if opening the JAR file fails for some reason
*/
public JarFileResourceLoader(final Path jarPath) throws IOException {
this.base = jarPath.toUri().toURL();
jarFile = new JarFile(jarPath.toFile());
}

/**
* Construct a new instance from a JAR file contained within a resource.
*
* @param resource the resource of the JAR file (must not be {@code null})
* @throws IOException if opening the JAR file fails for some reason
*/
public JarFileResourceLoader(final Resource resource) throws IOException {
// todo: this will be replaced with a version which opens the file in-place from a buffer
base = resource.url();
JarFile jf = null;
if (resource instanceof PathResource pr) {
try {
// avoid using a temp file, if possible
jf = new JarFile(pr.path().toFile(), true, JarFile.OPEN_READ, JarFile.runtimeVersion());
} catch (UnsupportedOperationException ignored) {
}
}
if (jf == null) {
tempFile = Files.createTempFile("srcr-tmp-", ".jar");
try {
resource.copyTo(tempFile);
jf = new JarFile(tempFile.toFile(), true, JarFile.OPEN_READ, JarFile.runtimeVersion());
} catch (Throwable t) {
try {
Files.delete(tempFile);
} catch (Throwable t2) {
t.addSuppressed(t2);
}
throw t;
}
}
jarFile = jf;
}

public Resource findResource(final String path) {
String canonPath = ResourceUtils.canonicalizeRelativePath(path);
JarEntry jarEntry = jarFile.getJarEntry(canonPath);
if (jarEntry != null) {
return new JarFileResource(base, jarFile, jarEntry);
} else {
jarEntry = jarFile.getJarEntry(canonPath + "/");
if (jarEntry != null) {
return new JarFileResource(base, jarFile, jarEntry);
} else {
return null;
}
}
}

public void close() {
try {
jarFile.close();
} catch (IOException ignored) {
}
if (tempFile != null) {
try {
Files.delete(tempFile);
} catch (IOException ignored) {
} finally {
tempFile = null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.smallrye.common.resource;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.util.Iterator;
import java.util.function.Function;

import io.smallrye.common.constraint.Assert;

/**
* A directory stream to map one kind of entry to another.
*/
public final class MappedDirectoryStream<T, R> implements DirectoryStream<R> {
private final DirectoryStream<T> delegate;
private final Function<T, R> mappingFunction;

/**
* Construct a new instance.
*
* @param delegate the delegate stream (must not be {@code null})
* @param mappingFunction the mapping function (must not be {@code null})
*/
public MappedDirectoryStream(final DirectoryStream<T> delegate, final Function<T, R> mappingFunction) {
this.delegate = Assert.checkNotNullParam("delegate", delegate);
this.mappingFunction = Assert.checkNotNullParam("mappingFunction", mappingFunction);
}

public Iterator<R> iterator() {
Iterator<T> itr = delegate.iterator();
return new Iterator<R>() {
public boolean hasNext() {
return itr.hasNext();
}

public R next() {
return mappingFunction.apply(itr.next());
}
};
}

public void close() throws IOException {
delegate.close();
}
}
Loading

0 comments on commit 8c0cd42

Please sign in to comment.