This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new ccdf06d5fc CAUSEWAY-3806: DataSource support for description and
access to underlying file
ccdf06d5fc is described below
commit ccdf06d5fc13dfe9f069763def4c328d60a9869b
Author: andi-huber <[email protected]>
AuthorDate: Fri Aug 9 15:02:24 2024 +0200
CAUSEWAY-3806: DataSource support for description and access to
underlying file
---
.../org/apache/causeway/commons/io/DataSource.java | 147 ++++++++++++++++++++-
.../org/apache/causeway/commons/io/ZipUtils.java | 15 +--
.../apache/causeway/commons/io/DataSourceTest.java | 86 ++++++++++++
3 files changed, 233 insertions(+), 15 deletions(-)
diff --git
a/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
index e3cdb15bb7..ada18e7084 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
@@ -25,6 +25,8 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -172,6 +174,9 @@ public interface DataSource {
@Override public <T> Try<T> tryReadAll(final @NonNull
Function<InputStream, Try<T>> consumingMapper) {
return
self.tryReadAll(is->consumingMapper.apply(inputStreamMapper.apply(is)));
}
+ @Override public String getDescription() {
+ return descriptionForMapped(self);
+ }
};
}
@@ -222,6 +227,13 @@ public interface DataSource {
@Override public <T> Try<T> tryReadAll(final @NonNull
Function<InputStream, Try<T>> consumingMapper) {
return Try.empty();
}
+ @Override public String getDescription() {
+ return descriptionForEmpty();
+ }
+ @Override
+ public String toString() {
+ return getDescription();
+ }
};
}
@@ -268,7 +280,9 @@ public interface DataSource {
return cls==null
|| _Strings.isNullOrEmpty(resourcePath)
? empty()
- :
ofInputStreamSupplier(()->cls.getResourceAsStream(resourcePath));
+ : ofInputStreamSupplierInternal(
+ descriptionForResource(cls, resourcePath),
+ ()->cls.getResourceAsStream(resourcePath));
}
/**
@@ -282,7 +296,10 @@ public interface DataSource {
static DataSource ofSpringResource(final @Nullable Resource
springResource) {
return springResource==null
? empty()
- : ofInputStreamSupplier(springResource::getInputStream);
+ : ofInputStreamSupplierInternal(
+ descriptionForResource(springResource),
+ fileForResource(springResource),
+ springResource::getInputStream);
}
/**
@@ -292,7 +309,9 @@ public interface DataSource {
static DataSource ofFile(final @Nullable File file) {
return file==null
? empty()
- : ofInputStreamSupplier(
+ : ofInputStreamSupplierInternal(
+ descriptionForFile(file),
+ Optional.of(file),
()->Try.call(()->new
FileInputStream(FileUtils.existingFileElseFail(file)))
.valueAsNonNullElseFail());
}
@@ -304,7 +323,9 @@ public interface DataSource {
static DataSource ofString(final @Nullable String string, final Charset
charset) {
return _Strings.isNullOrEmpty(string)
? empty()
- : ofInputStreamSupplier(()->new
ByteArrayInputStream(string.getBytes(charset)));
+ : ofInputStreamSupplierInternal(
+ descriptionForString(string),
+ ()->new
ByteArrayInputStream(string.getBytes(charset)));
}
/**
@@ -322,7 +343,123 @@ public interface DataSource {
static DataSource ofBytes(final @Nullable byte[] bytes) {
return _NullSafe.isEmpty(bytes)
? empty()
- : ofInputStreamSupplier(()->new ByteArrayInputStream(bytes));
+ : ofInputStreamSupplierInternal(
+ descriptionForBytes(bytes),
+ ()->new ByteArrayInputStream(bytes));
+ }
+
+ /**
+ * Optionally returns the underlying {@link File},
+ * based on whether this resource originates from a file.
+ */
+ default Optional<File> getFile() {
+ return Optional.empty();
+ }
+
+ /**
+ * The given file-consumer is either passed the underlying {@link File}
+ * (if this resource originates from a file),
+ * or a temporary file.
+ * <p>In the temporary file case, the temporary file is deleted after
consumption.
+ */
+ @SneakyThrows
+ default void consumeAsFile(ThrowingConsumer<File> fileConsumer) {
+ var file = getFile().orElse(null);
+ if(file!=null) {
+ fileConsumer.accept(file);
+ return;
+ }
+ var tempFile = File.createTempFile("causeway", "ds");
+ try {
+ tryReadAndWrite(DataSink.ofFile(tempFile), 4096);
+ fileConsumer.accept(tempFile);
+ } finally {
+ Files.deleteIfExists(tempFile.toPath()); // cleanup
+ }
+ }
+
+ /**
+ * Return a description for this DataSource,
+ * to be used for error output when working with the resource.
+ */
+ default String getDescription() {
+ return "";
+ }
+
+ // -- HELPER
+
+ // internal factory
+ private static DataSource ofInputStreamSupplierInternal(
+ final @NonNull String description,
+ final @NonNull ThrowingSupplier<InputStream> inputStreamSupplier) {
+ return ofInputStreamSupplierInternal(description, Optional.empty(),
inputStreamSupplier);
+ }
+
+ // internal factory
+ private static DataSource ofInputStreamSupplierInternal(
+ final @NonNull String description,
+ final Optional<File> file,
+ final @NonNull ThrowingSupplier<InputStream> inputStreamSupplier) {
+ return new DataSource() {
+ @Override public <T> Try<T> tryReadAll(final @NonNull
Function<InputStream, Try<T>> consumingMapper) {
+ return Try.call(()->{
+ try(final InputStream is = inputStreamSupplier.get()) {
+ return consumingMapper.apply(is);
+ }
+ })
+ // unwrap the inner try
+
.mapSuccessAsNullable(wrappedTry->wrappedTry.valueAsNullableElseFail());
+ }
+ @Override public Optional<File> getFile() {
+ return file;
+ }
+ @Override public String getDescription() {
+ return description;
+ }
+ @Override
+ public String toString() {
+ return description;
+ }
+ };
+ }
+
+ private static String descriptionForEmpty() {
+ return "Empty-Resource";
+ }
+
+ private static String descriptionForBytes(final byte[] bytes) {
+ if(bytes.length>16) {
+ byte[] sample = new byte[16];
+ System.arraycopy(bytes, 0, sample, 0, sample.length);
+ return String.format("Byte-Resource[%s ...]",
_Bytes.hexDump(sample));
+ }
+ return String.format("Byte-Resource[%s]", _Bytes.hexDump(bytes));
+ }
+
+ private static String descriptionForString(final String string) {
+ return String.format("String-Resource[%s]",
_Strings.ellipsifyAtEnd(string, 25, "..."));
+ }
+
+ private static String descriptionForResource(final Resource
springResource) {
+ return springResource.getDescription();
+ }
+
+ private static String descriptionForResource(final Class<?> cls, final
String resourcePath) {
+ return String.format("Class-Resource[%s, %s]", cls.getName(),
resourcePath);
+ }
+
+ private static String descriptionForMapped(DataSource ds) {
+ return ds.getDescription() + " mapped";
+ }
+
+ private static String descriptionForFile(File file) {
+ return String.format("File-Resource[%s]", file.getPath());
+ }
+
+ private static Optional<File> fileForResource(final Resource
springResource) {
+ return springResource.isFile()
+ ? Try.call(springResource::getFile).getValue()
+ : Optional.empty();
}
}
diff --git a/commons/src/main/java/org/apache/causeway/commons/io/ZipUtils.java
b/commons/src/main/java/org/apache/causeway/commons/io/ZipUtils.java
index 8c474a0d6e..b7c940741f 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/ZipUtils.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/ZipUtils.java
@@ -20,7 +20,6 @@ package org.apache.causeway.commons.io;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
-import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -141,11 +140,9 @@ public class ZipUtils {
final @NonNull ZipOptions zipOptions) {
val zipEntryDataSources = _Lists.<ZipEntryDataSource>newArrayList();
-
- var tempFile = File.createTempFile("causeway", "zip-utils");
- try {
- zippedSource.tryReadAndWrite(DataSink.ofFile(tempFile), 4096);
- try (FileSystem fs = FileSystems.newFileSystem(tempFile.toPath(),
null)) {
+
+ zippedSource.consumeAsFile(zipFile->{
+ try (FileSystem fs = FileSystems.newFileSystem(zipFile.toPath(),
null)) {
try (Stream<Path> entries = Files.walk(fs.getPath("/"))) {
final List<Path> filesInZip =
entries.filter(Files::isRegularFile).collect(Collectors.toList());
for(Path path : filesInZip) {
@@ -156,10 +153,8 @@ public class ZipUtils {
}
}
}
- } finally {
- Files.deleteIfExists(tempFile.toPath()); // cleanup
- }
-
+ });
+
return zipEntryDataSources.stream();
}
diff --git
a/commons/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
b/commons/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
new file mode 100644
index 0000000000..b0b29b2d94
--- /dev/null
+++ b/commons/src/test/java/org/apache/causeway/commons/io/DataSourceTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.commons.io;
+
+import java.io.File;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.springframework.core.io.FileSystemResource;
+
+class DataSourceTest {
+
+ @Test
+ void empty() {
+ var ds = DataSource.empty();
+ assertEquals("Empty-Resource", ds.getDescription());
+ assertEquals(Optional.empty(), ds.getFile());
+ }
+
+ @Test
+ void file() {
+ var file = new File("/path/to/file");
+ var ds = DataSource.ofFile(new File("/path/to/file"));
+ assertEquals(String.format("File-Resource[%s]", file.toString()),
ds.getDescription());
+ assertEquals(Optional.of(file), ds.getFile());
+ }
+
+ @Test
+ void bytes() {
+ var ds = DataSource.ofBytes(new byte[] {1, 2, 3, 9});
+ assertEquals(String.format("Byte-Resource[01 02 03 09]"),
ds.getDescription());
+ assertEquals(Optional.empty(), ds.getFile());
+ }
+
+ @Test
+ void string() {
+ var ds = DataSource.ofStringUtf8("Hello World!");
+ assertEquals("String-Resource[Hello World!]", ds.getDescription());
+ assertEquals(Optional.empty(), ds.getFile());
+ }
+
+ @Test
+ void classResource() {
+ var ds = DataSource.ofResource(getClass(), "/path/to/resource");
+
assertEquals(String.format("Class-Resource[org.apache.causeway.commons.io.DataSourceTest,
/path/to/resource]"), ds.getDescription());
+ assertEquals(Optional.empty(), ds.getFile());
+ }
+
+ @Test
+ void springResource() {
+ var file = new File("/path/to/file");
+ var resource = new FileSystemResource(file);
+ var ds = DataSource.ofSpringResource(resource);
+ assertEquals(String.format("file [%s]", file.getAbsolutePath()),
ds.getDescription());
+ assertEquals(Optional.of(file), ds.getFile());
+ }
+
+ @Test
+ void mapped() {
+ var file = new File("/path/to/file");
+ var ds = DataSource.ofFile(new File("/path/to/file"))
+ .map(is->is);
+ assertEquals(String.format("File-Resource[%s] mapped",
file.toString()), ds.getDescription());
+ assertEquals(Optional.empty(), ds.getFile());
+ }
+
+}