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());
+    }
+
+}

Reply via email to