diff --git a/beacon/build.gradle b/beacon/build.gradle index 064021d0b..eea5327dd 100644 --- a/beacon/build.gradle +++ b/beacon/build.gradle @@ -11,6 +11,10 @@ apply from: "$rootDir/deps/lombok.gradle" apply from: 'publish.gradle' apply from: "$rootDir/deps/publish-base.gradle" +configurations { + compileOnly.extendsFrom(dep) +} + version = file('../version').text group = 'io.xpipe' archivesBaseName = 'beacon' diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java new file mode 100644 index 000000000..301a53508 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java @@ -0,0 +1,49 @@ +package io.xpipe.beacon.exchange.cli; + +import io.xpipe.beacon.exchange.MessageExchange; +import io.xpipe.beacon.message.RequestMessage; +import io.xpipe.beacon.message.ResponseMessage; +import io.xpipe.core.source.DataSourceConfigInstance; +import io.xpipe.core.source.DataSourceId; +import io.xpipe.core.source.DataSourceReference; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +public class ConvertExchange implements MessageExchange { + + @Override + public String getId() { + return "convert"; + } + + @Override + public Class getRequestClass() { + return ConvertExchange.Request.class; + } + + @Override + public Class getResponseClass() { + return ConvertExchange.Response.class; + } + + @Jacksonized + @Builder + @Value + public static class Request implements RequestMessage { + @NonNull + DataSourceReference ref; + + @NonNull DataSourceId copyId; + + @NonNull + DataSourceConfigInstance config; + } + + @Jacksonized + @Builder + @Value + public static class Response implements ResponseMessage { + } +} diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index fde7b72a4..27799a0a4 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -33,12 +33,14 @@ module io.xpipe.beacon { WriteExecuteExchange, SelectExchange, ReadPreparationExchange, + QueryTextDataExchange, ReadExecuteExchange, DialogExchange, QueryDataSourceExchange, StoreStreamExchange, EditPreparationExchange, EditExecuteExchange, + ConvertExchange, QueryTableDataExchange, VersionExchange; } \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index 0d7423123..cd76f6065 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -12,6 +12,10 @@ apply from: "$rootDir/deps/junit.gradle" apply from: 'publish.gradle' apply from: "$rootDir/deps/publish-base.gradle" +configurations { + compileOnly.extendsFrom(dep) +} + version = file('../version').text group = 'io.xpipe' archivesBaseName = 'core' diff --git a/core/src/main/java/io/xpipe/core/source/CollectionDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/CollectionDataSourceDescriptor.java new file mode 100644 index 000000000..72c360cbb --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/CollectionDataSourceDescriptor.java @@ -0,0 +1,30 @@ +package io.xpipe.core.source; + +import io.xpipe.core.store.DataStore; + +public interface CollectionDataSourceDescriptor extends DataSourceDescriptor { + + @Override + default DataSourceInfo determineInfo(DS store) throws Exception { + try (var con = openReadConnection(store)) { + var c = (int) con.listEntries().count(); + return new DataSourceInfo.Structure(c); + } + } + + default CollectionReadConnection openReadConnection(DS store) throws Exception { + var con = newReadConnection(store); + con.init(); + return con; + } + + default CollectionWriteConnection openWriteConnection(DS store) throws Exception { + var con = newWriteConnection(store); + con.init(); + return con; + } + + CollectionWriteConnection newWriteConnection(DS store); + + CollectionReadConnection newReadConnection(DS store); +} diff --git a/core/src/main/java/io/xpipe/core/source/CollectionReadConnection.java b/core/src/main/java/io/xpipe/core/source/CollectionReadConnection.java new file mode 100644 index 000000000..c98f2219c --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/CollectionReadConnection.java @@ -0,0 +1,24 @@ +package io.xpipe.core.source; + +import lombok.SneakyThrows; + +import java.util.stream.Stream; + +public interface CollectionReadConnection extends DataSourceReadConnection { + + T open(String entry) throws Exception; + + Stream listEntries() throws Exception; + + @SneakyThrows + default void forward(DataSourceConnection con) throws Exception { + try (var tCon = (CollectionWriteConnection) con) { + tCon.init(); + listEntries().forEach(s -> { + try (var subCon = open(s)) { + ((CollectionWriteConnection) con).write(s, subCon); + } + }); + } + } +} diff --git a/core/src/main/java/io/xpipe/core/source/CollectionWriteConnection.java b/core/src/main/java/io/xpipe/core/source/CollectionWriteConnection.java new file mode 100644 index 000000000..a2242b364 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/CollectionWriteConnection.java @@ -0,0 +1,6 @@ +package io.xpipe.core.source; + +public interface CollectionWriteConnection extends DataSourceConnection { + + void write(String entry, DataSourceReadConnection con) throws Exception; +} diff --git a/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java index e5c3937d9..f18bfdf1e 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java @@ -27,11 +27,6 @@ public interface DataSourceDescriptor { */ DataSourceInfo determineInfo(DS store) throws Exception; - /** - * Returns the general data source type. - */ - DataSourceType getType(); - DataSourceReadConnection openReadConnection(DS store) throws Exception; DataSourceConnection openWriteConnection(DS store) throws Exception; diff --git a/core/src/main/java/io/xpipe/core/source/DataSourceInfo.java b/core/src/main/java/io/xpipe/core/source/DataSourceInfo.java index a75380fe2..b2c6c661c 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSourceInfo.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceInfo.java @@ -7,8 +7,6 @@ import io.xpipe.core.data.type.TupleType; import lombok.EqualsAndHashCode; import lombok.Value; -import java.nio.ByteOrder; -import java.nio.charset.Charset; import java.util.OptionalInt; /** @@ -53,8 +51,11 @@ public abstract class DataSourceInfo { @JsonTypeName("structure") public static class Structure extends DataSourceInfo { + int entries; + @JsonCreator - public Structure() { + public Structure(int entries) { + this.entries = entries; } @Override @@ -63,17 +64,32 @@ public abstract class DataSourceInfo { } } + @EqualsAndHashCode(callSuper = false) + @Value + @JsonTypeName("collection") + public static class Collection extends DataSourceInfo { + + int entries; + + @JsonCreator + public Collection(int entries) { + this.entries = entries; + } + + @Override + public DataSourceType getType() { + return DataSourceType.COLLECTION; + } + } + @EqualsAndHashCode(callSuper = false) @Value @JsonTypeName("text") public static class Text extends DataSourceInfo { - Charset charset; - int lineCount; @JsonCreator - public Text(Charset charset, int lineCount) { - this.charset = charset; + public Text(int lineCount) { this.lineCount = lineCount; } @@ -89,12 +105,10 @@ public abstract class DataSourceInfo { @JsonTypeName("raw") public static class Raw extends DataSourceInfo { int byteCount; - ByteOrder byteOrder; @JsonCreator - public Raw(int byteCount, ByteOrder byteOrder) { + public Raw(int byteCount) { this.byteCount = byteCount; - this.byteOrder = byteOrder; } @Override diff --git a/core/src/main/java/io/xpipe/core/source/DataSourceType.java b/core/src/main/java/io/xpipe/core/source/DataSourceType.java index be4d78623..d6c617a47 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSourceType.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceType.java @@ -18,5 +18,8 @@ public enum DataSourceType { TEXT, @JsonProperty("raw") - RAW + RAW, + + @JsonProperty("collection") + COLLECTION } diff --git a/core/src/main/java/io/xpipe/core/source/RawDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/RawDataSourceDescriptor.java new file mode 100644 index 000000000..fe79bd783 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/RawDataSourceDescriptor.java @@ -0,0 +1,35 @@ +package io.xpipe.core.source; + +import io.xpipe.core.store.DataStore; + +public abstract class RawDataSourceDescriptor implements DataSourceDescriptor { + + private static final int MAX_BYTES_READ = 100000; + + @Override + public DataSourceInfo determineInfo(DS store) throws Exception { + try (var con = openReadConnection(store)) { + var b = con.readBytes(MAX_BYTES_READ); + int usedCount = b.length == MAX_BYTES_READ ? -1 : b.length; + return new DataSourceInfo.Raw(usedCount); + } + } + + @Override + public RawReadConnection openReadConnection(DS store) throws Exception { + var con = newReadConnection(store); + con.init(); + return con; + } + + @Override + public RawWriteConnection openWriteConnection(DS store) throws Exception { + var con = newWriteConnection(store); + con.init(); + return con; + } + + protected abstract RawWriteConnection newWriteConnection(DS store); + + protected abstract RawReadConnection newReadConnection(DS store); +} diff --git a/core/src/main/java/io/xpipe/core/source/RawReadConnection.java b/core/src/main/java/io/xpipe/core/source/RawReadConnection.java new file mode 100644 index 000000000..725b537f4 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/RawReadConnection.java @@ -0,0 +1,18 @@ +package io.xpipe.core.source; + +public interface RawReadConnection extends DataSourceReadConnection { + + byte[] readBytes(int max) throws Exception; + + int BUFFER_SIZE = 8192; + + default void forward(DataSourceConnection con) throws Exception { + try (var tCon = (RawWriteConnection) con) { + tCon.init(); + byte[] b; + while ((b = readBytes(BUFFER_SIZE)).length > 0) { + tCon.write(b); + } + } + } +} diff --git a/core/src/main/java/io/xpipe/core/source/RawWriteConnection.java b/core/src/main/java/io/xpipe/core/source/RawWriteConnection.java new file mode 100644 index 000000000..3ce72c83f --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/RawWriteConnection.java @@ -0,0 +1,6 @@ +package io.xpipe.core.source; + +public interface RawWriteConnection extends DataSourceConnection { + + void write(byte[] bytes) throws Exception; +} diff --git a/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java index 47a2e9722..8a91d5e7d 100644 --- a/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java +++ b/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java @@ -1,27 +1,43 @@ package io.xpipe.core.source; +import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.store.DataStore; -public abstract class StructureDataSourceDescriptor implements DataSourceDescriptor { +public interface StructureDataSourceDescriptor extends DataSourceDescriptor { - public final StructureReadConnection openReadConnection(DS store) throws Exception { + private int countEntries(DataStructureNode n) { + if (n.isValue()) { + return 1; + } + + int c = 0; + for (int i = 0; i < n.size(); i++) { + c += countEntries(n.at(i)); + } + return c; + } + + @Override + default DataSourceInfo determineInfo(DS store) throws Exception { + try (var con = openReadConnection(store)) { + var n = con.read(); + var c = countEntries(n); + return new DataSourceInfo.Structure(c); + } + } + + default StructureReadConnection openReadConnection(DS store) throws Exception { var con = newReadConnection(store); con.init(); return con; } - public final StructureWriteConnection openWriteConnection(DS store) throws Exception { + default StructureWriteConnection openWriteConnection(DS store) throws Exception { var con = newWriteConnection(store); con.init(); return con; } + StructureWriteConnection newWriteConnection(DS store); - protected abstract StructureWriteConnection newWriteConnection(DS store); - - protected abstract StructureReadConnection newReadConnection(DS store); - - @Override - public DataSourceType getType() { - return DataSourceType.STRUCTURE; - } + StructureReadConnection newReadConnection(DS store); } diff --git a/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java index 003903bbe..9f6089b26 100644 --- a/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java +++ b/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java @@ -19,9 +19,4 @@ public abstract class TableDataSourceDescriptor implements protected abstract TableWriteConnection newWriteConnection(DS store); protected abstract TableReadConnection newReadConnection(DS store); - - @Override - public DataSourceType getType() { - return DataSourceType.TABLE; - } } diff --git a/core/src/main/java/io/xpipe/core/source/TextDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/TextDataSourceDescriptor.java index b2a9c2734..4eca37246 100644 --- a/core/src/main/java/io/xpipe/core/source/TextDataSourceDescriptor.java +++ b/core/src/main/java/io/xpipe/core/source/TextDataSourceDescriptor.java @@ -4,9 +4,15 @@ import io.xpipe.core.store.DataStore; public abstract class TextDataSourceDescriptor implements DataSourceDescriptor { + private static final int MAX_LINE_READ = 1000; + @Override - public DataSourceType getType() { - return DataSourceType.TEXT; + public DataSourceInfo determineInfo(DS store) throws Exception { + try (var con = openReadConnection(store)) { + int count = (int) con.lines().limit(MAX_LINE_READ).count(); + int usedCount = count == MAX_LINE_READ ? -1 : count; + return new DataSourceInfo.Text(usedCount); + } } @Override diff --git a/core/src/main/java/io/xpipe/core/source/TextReadConnection.java b/core/src/main/java/io/xpipe/core/source/TextReadConnection.java index c69456e48..7081cca28 100644 --- a/core/src/main/java/io/xpipe/core/source/TextReadConnection.java +++ b/core/src/main/java/io/xpipe/core/source/TextReadConnection.java @@ -1,23 +1,31 @@ package io.xpipe.core.source; -import java.util.List; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.stream.Stream; public interface TextReadConnection extends DataSourceReadConnection { - /** - * Reads the complete contents. - */ - String readAll() throws Exception; - - List readAllLines() throws Exception; - - String readLine() throws Exception; - Stream lines() throws Exception; boolean isFinished() throws Exception; + default void forwardLines(OutputStream out, int maxLines) throws Exception { + if (maxLines == 0) { + return; + } + + int counter = 0; + for (var it = lines().iterator(); it.hasNext(); counter++) { + if (counter == maxLines) { + break; + } + + out.write(it.next().getBytes(StandardCharsets.UTF_8)); + out.write("\n".getBytes(StandardCharsets.UTF_8)); + } + } + default void forward(DataSourceConnection con) throws Exception { try (var tCon = (TextWriteConnection) con) { tCon.init(); diff --git a/core/src/main/java/io/xpipe/core/util/JacksonHelper.java b/core/src/main/java/io/xpipe/core/util/JacksonHelper.java index 7eb40c47e..684398682 100644 --- a/core/src/main/java/io/xpipe/core/util/JacksonHelper.java +++ b/core/src/main/java/io/xpipe/core/util/JacksonHelper.java @@ -1,6 +1,7 @@ package io.xpipe.core.util; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -22,6 +23,7 @@ public class JacksonHelper { ObjectMapper objectMapper = INSTANCE; objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.registerModules(findModules(layer)); objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker() diff --git a/extension/build.gradle b/extension/build.gradle index 68e99dec1..bd93d5e18 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -17,6 +17,10 @@ apply from: "$rootDir/deps/slf4j.gradle" apply from: 'publish.gradle' apply from: "$rootDir/deps/publish-base.gradle" +configurations { + compileOnly.extendsFrom(dep) +} + version = file('../version').text group = 'io.xpipe' archivesBaseName = 'extension' diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index eb25189a2..ec195a458 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -12,31 +12,74 @@ import javafx.scene.layout.Region; import java.nio.charset.Charset; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; public interface DataSourceProvider { + default String i18n(String key) { + return I18n.get(getId() + "." + key); + } + + default String i18nKey(String key) { + return getId() + "." + key; + } + + default Region createConfigOptions(DataStore input, Property> source) { + return null; + } + + default String getDisplayName() { + return i18n("displayName"); + } + + default String getDisplayImageFile() { + return "logo.png"; + } + + default String getDescription(DataSourceDescriptor source) { + return i18n("description"); + } + interface FileProvider { String getFileName(); - Map, String> getFileExtensions(); - } - - interface GuiProvider { - - Region createConfigOptions(DataStore input, Property> source); - - String getDisplayName(); - - String getDisplayImage(); - - Supplier getDescription(DataSourceDescriptor source); + Map> getFileExtensions(); } interface ConfigProvider { + static ConfigProvider empty(List names, Supplier> supplier) { + return new ConfigProvider() { + @Override + public ConfigOptionSet getConfig() { + return ConfigOptionSet.empty(); + } + + @Override + public DataSourceDescriptor toDescriptor(Map values) { + return supplier.get(); + } + + @Override + public Map toConfigOptions(DataSourceDescriptor desc) { + return Map.of(); + } + + @Override + public Map> getConverters() { + return Map.of(); + } + + @Override + public List getPossibleNames() { + return names; + } + }; + } + ConfigOption CHARSET_OPTION = new ConfigOption("Charset", "charset"); Function @@ -87,19 +130,45 @@ public interface DataSourceProvider { List getPossibleNames(); } - DataSourceType getType(); + default boolean isHidden() { + return false; + } + DataSourceType getPrimaryType(); + + /** + * Checks whether this provider prefers a certain kind of store. + * This is important for the correct autodetection of a store. + */ boolean prefersStore(DataStore store); - boolean supportsStore(DataStore store); + /** + * Checks whether this provider supports the store in principle. + * This method should not perform any further checks, + * just check whether it may be possible that the store is supported. + * + * This method will be called for validation purposes. + */ + boolean couldSupportStore(DataStore store); + + /** + * Performs a deep inspection to check whether this provider supports a given store. + * + * This functionality will be used in case no preferred provider has been found. + */ + default boolean supportsStore(DataStore store) { + return false; + } FileProvider getFileProvider(); - GuiProvider getGuiProvider(); - ConfigProvider getConfigProvider(); - String getId(); + default String getId() { + var n = getClass().getPackageName(); + var i = n.lastIndexOf('.'); + return i != -1 ? n.substring(i + 1) : n; + } DataSourceDescriptor createDefaultDescriptor(); @@ -112,4 +181,6 @@ public interface DataSourceProvider { DataSourceDescriptor createDefaultWriteDescriptor(DataStore input, DataSourceInfo info) throws Exception; Class> getDescriptorClass(); + + Optional determineDefaultName(DataStore store); } diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java index fee2a2b77..f2c3960f3 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java @@ -1,12 +1,13 @@ package io.xpipe.extension; import io.xpipe.core.data.type.TupleType; -import io.xpipe.core.source.DataSourceType; -import io.xpipe.core.source.TableDataSourceDescriptor; +import io.xpipe.core.source.*; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.LocalFileDataStore; -import io.xpipe.extension.event.ErrorEvent; +import io.xpipe.core.store.StreamDataStore; +import lombok.SneakyThrows; +import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; @@ -23,35 +24,54 @@ public class DataSourceProviders { } } - public static DataSourceProvider getNativeProviderForType(DataSourceType t) { - switch (t) { - case TABLE -> { - return DataSourceProviders.byId("xpbt").orElseThrow(); - } - case STRUCTURE -> { - return DataSourceProviders.byId("xpbs").orElseThrow(); - } - case TEXT -> { - return DataSourceProviders.byId("xpbx").orElseThrow(); - } - case RAW -> { - return DataSourceProviders.byId("xpbb").orElseThrow(); - } + @SuppressWarnings("unchecked") + public static DataSourceDescriptor getNativeDataSourceDescriptorForType(DataSourceType t) { + try { + return switch (t) { + case TABLE -> (DataSourceDescriptor) DataSourceProviders.byId("xpbt").orElseThrow() + .getDescriptorClass().getConstructors()[0].newInstance(); + case STRUCTURE -> (DataSourceDescriptor) DataSourceProviders.byId("xpbs").orElseThrow() + .getDescriptorClass().getConstructors()[0].newInstance(); + case TEXT -> (DataSourceDescriptor) DataSourceProviders.byId("text").orElseThrow() + .getDescriptorClass().getConstructors()[0].newInstance(StandardCharsets.UTF_8); + case RAW -> (DataSourceDescriptor) DataSourceProviders.byId("xpbr").orElseThrow() + .getDescriptorClass().getConstructors()[0].newInstance(); + }; + } catch (Exception ex) { + throw new AssertionError(ex); } - - throw new AssertionError(); } @SuppressWarnings("unchecked") + @SneakyThrows + public static StructureDataSourceDescriptor createLocalStructureDescriptor() { + return (StructureDataSourceDescriptor) + DataSourceProviders.byId("json").orElseThrow().getDescriptorClass() + .getDeclaredConstructors()[0].newInstance(); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public static RawDataSourceDescriptor createLocalRawDescriptor() { + return (RawDataSourceDescriptor) + DataSourceProviders.byId("binary").orElseThrow().getDescriptorClass() + .getDeclaredConstructors()[0].newInstance(); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public static TextDataSourceDescriptor createLocalTextDescriptor() { + return (TextDataSourceDescriptor) + DataSourceProviders.byId("text").orElseThrow().getDescriptorClass() + .getDeclaredConstructors()[0].newInstance(); + } + + @SuppressWarnings("unchecked") + @SneakyThrows public static TableDataSourceDescriptor createLocalTableDescriptor(TupleType type) { - try { - return (TableDataSourceDescriptor) - DataSourceProviders.byId("xpbt").orElseThrow().getDescriptorClass() - .getDeclaredConstructors()[0].newInstance(type); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); - return null; - } + return (TableDataSourceDescriptor) + DataSourceProviders.byId("xpbt").orElseThrow().getDescriptorClass() + .getDeclaredConstructors()[0].newInstance(type); } public static Optional byDescriptorClass(Class clazz) { @@ -85,7 +105,7 @@ public class DataSourceProviders { } return ALL.stream().filter(d -> d.getFileProvider() != null) - .filter(d -> d.supportsStore(store)).findAny(); + .filter(d -> d.couldSupportStore(store)).findAny(); } public static Set getAll() { diff --git a/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java new file mode 100644 index 000000000..f3a359045 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java @@ -0,0 +1,64 @@ +package io.xpipe.extension; + +import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.FileDataStore; +import io.xpipe.core.store.StreamDataStore; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface SimpleFileDataSourceProvider extends DataSourceProvider { + + @Override + default Optional determineDefaultName(DataStore store) { + if (store instanceof FileDataStore l) { + var n = l.getFileName(); + var i = n.lastIndexOf('.'); + return Optional.of(i != -1 ? n.substring(0, i) : n); + } + + return Optional.empty(); + } + + @Override + default boolean prefersStore(DataStore store) { + for (var e : getSupportedExtensions().entrySet()) { + if (e.getValue() == null) { + continue; + } + + if (store instanceof FileDataStore l) { + return l.getFileName().matches("\\." + e.getValue() + "$"); + } + } + return false; + } + + @Override + default boolean couldSupportStore(DataStore store) { + return store instanceof StreamDataStore; + } + + default String getNameI18nKey() { + return i18nKey("displayName"); + } + Map> getSupportedExtensions(); + + @Override + default FileProvider getFileProvider() { + return new FileProvider() { + @Override + public String getFileName() { + return I18n.get(getNameI18nKey()); + } + + @Override + public Map> getFileExtensions() { + var map = new LinkedHashMap<>(getSupportedExtensions()); + return map; + } + }; + } +} diff --git a/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java new file mode 100644 index 000000000..aa7a41ca2 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java @@ -0,0 +1,35 @@ +package io.xpipe.extension; + +import io.xpipe.core.source.DataSourceDescriptor; +import io.xpipe.core.source.DataSourceInfo; +import io.xpipe.core.store.DataStore; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +public interface UniformDataSourceProvider extends DataSourceProvider { + + @Override + default ConfigProvider getConfigProvider() { + return ConfigProvider.empty(List.of(getId()), this::createDefaultDescriptor); + } + + @Override + default DataSourceDescriptor createDefaultDescriptor() { + try { + return (DataSourceDescriptor) getDescriptorClass().getDeclaredConstructors()[0].newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + } + + @Override + default DataSourceDescriptor createDefaultDescriptor(DataStore input) throws Exception { + return createDefaultDescriptor(); + } + + @Override + default DataSourceDescriptor createDefaultWriteDescriptor(DataStore input, DataSourceInfo info) throws Exception { + return createDefaultDescriptor(); + } +} diff --git a/extension/src/main/java/module-info.java b/extension/src/main/java/module-info.java index 60ec87930..e41365388 100644 --- a/extension/src/main/java/module-info.java +++ b/extension/src/main/java/module-info.java @@ -7,10 +7,10 @@ module io.xpipe.extension { exports io.xpipe.extension.event; exports io.xpipe.extension.prefs; - requires io.xpipe.core; - requires javafx.base; + requires transitive io.xpipe.core; + requires transitive javafx.base; requires javafx.graphics; - requires javafx.controls; + requires transitive javafx.controls; requires io.xpipe.fxcomps; requires org.apache.commons.collections4; requires static lombok; diff --git a/samples/sample_extension/build.gradle b/samples/sample_extension/build.gradle deleted file mode 100644 index e69de29bb..000000000 diff --git a/samples/sample_extensions/file_data_source_sample/build.gradle b/samples/sample_extensions/file_data_source_sample/build.gradle new file mode 100644 index 000000000..35438ca8d --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'java' + id "org.moditect.gradleplugin" version "1.0.0-rc3" +} + +apply from: "$rootDir/deps/java.gradle" +apply from: "$rootDir/deps/javafx.gradle" +apply from: "$rootDir/deps/lombok.gradle" +apply from: "$rootDir/deps/extension.gradle" + +configurations { + compileOnly.extendsFrom(dep) +} diff --git a/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileDescriptor.java b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileDescriptor.java new file mode 100644 index 000000000..fbe46c1f3 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileDescriptor.java @@ -0,0 +1,18 @@ +package io.xpipe.ext.json; + +import io.xpipe.core.source.RawDataSourceDescriptor; +import io.xpipe.core.source.RawReadConnection; +import io.xpipe.core.source.RawWriteConnection; +import io.xpipe.core.store.StreamDataStore; + +public class MyRawFileDescriptor extends RawDataSourceDescriptor { + @Override + protected RawWriteConnection newWriteConnection(StreamDataStore store) { + return new MyRawFileWriteConnection(store); + } + + @Override + protected RawReadConnection newReadConnection(StreamDataStore store) { + return new MyRawFileReadConnection(store); + } +} diff --git a/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileProvider.java b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileProvider.java new file mode 100644 index 000000000..2b078bfe9 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileProvider.java @@ -0,0 +1,30 @@ +package io.xpipe.ext.json; + +import io.xpipe.core.source.DataSourceDescriptor; +import io.xpipe.core.source.DataSourceType; +import io.xpipe.extension.DataSourceProvider; +import io.xpipe.extension.SimpleFileDataSourceProvider; +import io.xpipe.extension.UniformDataSourceProvider; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class MyRawFileProvider implements UniformDataSourceProvider, SimpleFileDataSourceProvider, DataSourceProvider { + + @Override + public DataSourceType getPrimaryType() { + return DataSourceType.RAW; + } + + @Override + public Map getSupportedExtensions() { + var map = new LinkedHashMap(); + map.put(i18nKey("fileName"), "myf"); + return map; + } + + @Override + public Class> getDescriptorClass() { + return MyRawFileDescriptor.class; + } +} diff --git a/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileReadConnection.java b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileReadConnection.java new file mode 100644 index 000000000..bf91cc428 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileReadConnection.java @@ -0,0 +1,43 @@ +package io.xpipe.ext.json; + +import io.xpipe.core.source.RawReadConnection; +import io.xpipe.core.store.StreamDataStore; + +import java.io.InputStream; + +public class MyRawFileReadConnection implements RawReadConnection { + + private InputStream inputStream; + private final StreamDataStore store; + + public MyRawFileReadConnection(StreamDataStore store) { + this.store = store; + } + + @Override + public void init() throws Exception { + if (inputStream != null) { + throw new IllegalStateException("Already initialized"); + } + + inputStream = store.openInput(); + } + + @Override + public void close() throws Exception { + if (inputStream == null) { + throw new IllegalStateException("Not initialized"); + } + + inputStream.close(); + } + + @Override + public byte[] readBytes(int max) throws Exception { + if (inputStream == null) { + throw new IllegalStateException("Not initialized"); + } + + return inputStream.readNBytes(max); + } +} diff --git a/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileWriteConnection.java b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileWriteConnection.java new file mode 100644 index 000000000..be1f21028 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/java/io/xpipe/ext/file_data_source_sample/MyRawFileWriteConnection.java @@ -0,0 +1,43 @@ +package io.xpipe.ext.json; + +import io.xpipe.core.source.RawWriteConnection; +import io.xpipe.core.store.StreamDataStore; + +import java.io.OutputStream; + +public class MyRawFileWriteConnection implements RawWriteConnection { + + private final StreamDataStore store; + private OutputStream outputStream; + + public MyRawFileWriteConnection(StreamDataStore store) { + this.store = store; + } + + @Override + public void init() throws Exception { + if (outputStream != null) { + throw new IllegalStateException("Already initialized"); + } + + outputStream = store.openOutput(); + } + + @Override + public void close() throws Exception { + if (outputStream == null) { + throw new IllegalStateException("Not initialized"); + } + + outputStream.close(); + } + + @Override + public void write(byte[] bytes) throws Exception { + if (outputStream == null) { + throw new IllegalStateException("Not initialized"); + } + + outputStream.write(bytes); + } +} diff --git a/samples/sample_extensions/file_data_source_sample/src/main/java/module-info.java b/samples/sample_extensions/file_data_source_sample/src/main/java/module-info.java new file mode 100644 index 000000000..70aa944a9 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/java/module-info.java @@ -0,0 +1,13 @@ +import io.xpipe.ext.json.MyRawFileProvider; +import io.xpipe.extension.DataSourceProvider; + +module io.xpipe.ext.file_data_source_sample { + exports io.xpipe.ext.json; + + opens io.xpipe.ext.json; + + requires io.xpipe.core; + requires io.xpipe.extension; + + provides DataSourceProvider with MyRawFileProvider; +} \ No newline at end of file diff --git a/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/img/icon.png b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/img/icon.png new file mode 100644 index 000000000..135b5b83e Binary files /dev/null and b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/img/icon.png differ diff --git a/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_de.properties b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_de.properties new file mode 100644 index 000000000..d86dcac53 --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_de.properties @@ -0,0 +1,3 @@ +displayName=Mein Dateiformat +description=Meine Dateiformat-Beschreibung +fileName=Mein Dateiformat Datei \ No newline at end of file diff --git a/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_en.properties b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_en.properties new file mode 100644 index 000000000..55e578f9e --- /dev/null +++ b/samples/sample_extensions/file_data_source_sample/src/main/resources/io/xpipe/ext/file_data_source_sample/resources/lang/translations_en.properties @@ -0,0 +1,3 @@ +displayName=My file format +description=My file format description +fileName=My file format file \ No newline at end of file