Change structure of data sources, rework comps

This commit is contained in:
Christopher Schnick 2022-06-06 23:59:57 +02:00
parent 948dcf33ef
commit f4f9c1d978
40 changed files with 719 additions and 193 deletions

View file

@ -60,7 +60,7 @@ public abstract class DataSourceImpl implements DataSource {
}); });
var configInstance = startRes.getConfig(); var configInstance = startRes.getConfig();
configInstance.getCurrentValues().putAll(config); configInstance.getConfigInstance().getCurrentValues().putAll(config);
var endReq = ReadExecuteExchange.Request.builder() var endReq = ReadExecuteExchange.Request.builder()
.target(id).dataStore(store).config(configInstance).build(); .target(id).dataStore(store).config(configInstance).build();
XPipeConnection.execute(con -> { XPipeConnection.execute(con -> {

View file

@ -37,6 +37,8 @@ public class ReadPreparationExchange implements MessageExchange<ReadPreparationE
@NonNull @NonNull
StreamDataStore store; StreamDataStore store;
boolean configureAll;
} }
@Jacksonized @Jacksonized
@ -45,6 +47,7 @@ public class ReadPreparationExchange implements MessageExchange<ReadPreparationE
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
String determinedType; String determinedType;
@NonNull
DataSourceConfigInstance config; DataSourceConfigInstance config;
} }
} }

View file

@ -38,7 +38,6 @@ public class DialogExchange implements MessageExchange<DialogExchange.Request, D
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
DataSourceConfigInstance instance;
String errorMsg; String errorMsg;
} }
} }

View file

@ -7,6 +7,8 @@ apply from: "$rootDir/deps/java.gradle"
apply from: "$rootDir/deps/commons.gradle" apply from: "$rootDir/deps/commons.gradle"
apply from: "$rootDir/deps/junit.gradle" apply from: "$rootDir/deps/junit.gradle"
apply from: "$rootDir/deps/lombok.gradle" apply from: "$rootDir/deps/lombok.gradle"
apply from: "$rootDir/deps/jackson.gradle"
configurations { configurations {
compileOnly.extendsFrom(dep) compileOnly.extendsFrom(dep)

View file

@ -1,14 +1,19 @@
package io.xpipe.charsetter; package io.xpipe.charsetter;
import lombok.Value;
import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.function.FailableBiConsumer; import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier; import org.apache.commons.lang3.function.FailableSupplier;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.*; import java.nio.charset.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class Charsetter { public class Charsetter {
@ -25,7 +30,13 @@ public class Charsetter {
} }
} }
public static Charset read(FailableSupplier<InputStream, Exception> in, FailableBiConsumer<InputStream, Charset, Exception> con) throws Exception { @Value
public static class Result {
Charset charset;
NewLine newLine;
}
public static Result read(FailableSupplier<InputStream, Exception> in, FailableConsumer<InputStreamReader, Exception> con) throws Exception {
checkInit(); checkInit();
try (var is = in.get(); try (var is = in.get();
@ -34,20 +45,53 @@ public class Charsetter {
String charsetName = bom == null ? null : bom.getCharsetName(); String charsetName = bom == null ? null : bom.getCharsetName();
var charset = charsetName != null ? Charset.forName(charsetName) : null; var charset = charsetName != null ? Charset.forName(charsetName) : null;
bin.mark(MAX_BYTES);
var bytes = bin.readNBytes(MAX_BYTES);
bin.reset();
if (charset == null) { if (charset == null) {
bin.mark(MAX_BYTES);
var bytes = bin.readNBytes(MAX_BYTES);
bin.reset();
charset = inferCharset(bytes); charset = inferCharset(bytes);
} }
var nl = inferNewLine(bytes);
if (con != null) { if (con != null) {
con.accept(bin, charset); con.accept(new InputStreamReader(bin, charset));
} }
return charset; return new Result(charset, nl);
} }
} }
public static NewLine inferNewLine(byte[] content) {
Map<NewLine, Integer> count = new HashMap<>();
for (var nl : NewLine.values()) {
var nlBytes = nl.getNewLine().getBytes(StandardCharsets.UTF_8);
count.put(nl, count(content, nlBytes));
}
if (count.values().stream().allMatch(v -> v == 0)) {
return null;
}
return count.entrySet().stream().min(Comparator.comparingInt(Map.Entry::getValue))
.orElseThrow().getKey();
}
public static int count(byte[] outerArray, byte[] smallerArray) {
int count = 0;
for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
boolean found = true;
for(int j = 0; j < smallerArray.length; ++j) {
if (outerArray[i+j] != smallerArray[j]) {
found = false;
break;
}
}
if (found) {
count++;
}
}
return count;
}
public static Charset inferCharset(byte[] content) { public static Charset inferCharset(byte[] content) {
checkInit(); checkInit();

View file

@ -3,6 +3,7 @@ package io.xpipe.charsetter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;
import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -10,6 +11,10 @@ import java.util.Locale;
@AllArgsConstructor @AllArgsConstructor
public class CharsetterContext { public class CharsetterContext {
public static CharsetterContext empty() {
return new CharsetterContext(Charset.defaultCharset().name(), Locale.getDefault(), Locale.getDefault(), List.of());
}
String systemCharsetName; String systemCharsetName;
Locale systemLocale; Locale systemLocale;

View file

@ -0,0 +1,41 @@
package io.xpipe.charsetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
public enum NewLine {
@JsonProperty("lf")
LF("\n", "lf"),
@JsonProperty("crlf")
CRLF("\r\n", "crlf");
public static NewLine platform() {
return Arrays.stream(values())
.filter(n -> n.getNewLine().equals(System.getProperty("line.separator")))
.findFirst().orElseThrow();
}
public static NewLine id(String id) {
return Arrays.stream(values())
.filter(n -> n.getId().equalsIgnoreCase(id))
.findFirst().orElseThrow();
}
private final String newLine;
private final String id;
NewLine(String newLine, String id) {
this.newLine = newLine;
this.id = id;
}
public String getNewLine() {
return newLine;
}
public String getId() {
return id;
}
}

View file

@ -4,4 +4,5 @@ module io.xpipe.charsetter {
requires org.apache.commons.io; requires org.apache.commons.io;
requires org.apache.commons.lang3; requires org.apache.commons.lang3;
requires static lombok; requires static lombok;
requires com.fasterxml.jackson.databind;
} }

View file

@ -0,0 +1,86 @@
package io.xpipe.core.config;
import java.nio.charset.Charset;
public abstract class ConfigConverter<T> {
public static final ConfigConverter<Charset> CHARSET = new ConfigConverter<Charset>() {
@Override
protected Charset fromString(String s) {
return Charset.forName(s);
}
@Override
protected String toString(Charset value) {
return value.name();
}
};
public static final ConfigConverter<String> STRING = new ConfigConverter<String>() {
@Override
protected String fromString(String s) {
return s;
}
@Override
protected String toString(String value) {
return value;
}
};
public static final ConfigConverter<Character> CHARACTER = new ConfigConverter<Character>() {
@Override
protected Character fromString(String s) {
if (s.length() != 1) {
throw new IllegalArgumentException("Invalid character: " + s);
}
return s.toCharArray()[0];
}
@Override
protected String toString(Character value) {
return value.toString();
}
};
public static final ConfigConverter<Boolean> BOOLEAN = new ConfigConverter<Boolean>() {
@Override
protected Boolean fromString(String s) {
if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("true")) {
return true;
}
if (s.equalsIgnoreCase("n") || s.equalsIgnoreCase("no") || s.equalsIgnoreCase("false")) {
return false;
}
throw new IllegalArgumentException("Invalid boolean: " + s);
}
@Override
protected String toString(Boolean value) {
return value ? "yes" : "no";
}
};
public T convertFromString(String s) {
if (s == null) {
return null;
}
return fromString(s);
}
public String convertToString(T value) {
if (value == null) {
return null;
}
return toString(value);
}
protected abstract T fromString(String s);
protected abstract String toString(T value);
}

View file

@ -1,14 +0,0 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
@AllArgsConstructor(onConstructor_={@JsonCreator})
public class ConfigOption {
String name;
String key;
}

View file

@ -1,23 +0,0 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Singular;
import lombok.Value;
import java.util.List;
@Value
@Builder
@AllArgsConstructor(onConstructor_={@JsonCreator})
public class ConfigOptionSet {
public static ConfigOptionSet empty() {
return new ConfigOptionSet(List.of());
}
@Singular
List<ConfigOption> options;
}

View file

@ -1,26 +0,0 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Value;
import java.util.Map;
@Value
@AllArgsConstructor(onConstructor_={@JsonCreator})
public class ConfigOptionSetInstance {
/**
* The available configuration options.
*/
ConfigOptionSet configOptions;
/**
* The current configuration options that are set.
*/
Map<String, String> currentValues;
public boolean isComplete() {
return currentValues.size() == configOptions.getOptions().size();
}
}

View file

@ -0,0 +1,26 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Value;
@Value
@AllArgsConstructor
public class ConfigParameter {
String key;
@JsonCreator
public ConfigParameter(String key) {
this.key = key;
this.converter = null;
}
@JsonIgnore
ConfigConverter<?> converter;
@SuppressWarnings("unchecked")
public <T> ConfigConverter<T> getConverter() {
return (ConfigConverter<T>) converter;
}
}

View file

@ -0,0 +1,54 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Value;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Value
@AllArgsConstructor(onConstructor_={@JsonCreator})
public class ConfigParameterSetInstance {
/**
* The available configuration parameters.
*/
List<ConfigParameter> configParameters;
/**
* The current configuration options that are set.
*/
Map<String, String> currentValues;
public ConfigParameterSetInstance(Map<ConfigParameter, Object> map) {
configParameters = map.keySet().stream().toList();
currentValues = map.entrySet().stream().collect(Collectors.toMap(
e -> e.getKey().getKey(),
e -> e.getKey().getConverter().convertToString(e.getValue())));
}
public <X, T extends Function<X,?>> ConfigParameterSetInstance(Map<ConfigParameter, T> map, Object v) {
configParameters = map.keySet().stream().toList();
currentValues = map.entrySet().stream().collect(Collectors.toMap(
e -> e.getKey().getKey(),
e -> e.getKey().getConverter().convertToString(apply(e.getValue(), v))));
}
@SuppressWarnings("unchecked")
private static <X, T extends Function<X,?>, V> Object apply(T func, Object v) {
return func.apply((X) v);
}
public void update(ConfigParameter p, String val) {
currentValues.put(p.getKey(), val);
}
public Map<ConfigParameter, Object> evaluate() {
return configParameters.stream().collect(Collectors.toMap(
p -> p,
p -> p.getConverter().convertFromString(currentValues.get(p.getKey()))));
}
}

View file

@ -0,0 +1,4 @@
package io.xpipe.core.connection;
public class Connection {
}

View file

@ -8,6 +8,10 @@ import java.util.stream.Collectors;
public abstract class ArrayNode extends DataStructureNode { public abstract class ArrayNode extends DataStructureNode {
public static ArrayNode empty() {
return of(List.of());
}
public static ArrayNode of(DataStructureNode... dsn) { public static ArrayNode of(DataStructureNode... dsn) {
return of(List.of(dsn)); return of(List.of(dsn));
} }

View file

@ -4,7 +4,8 @@ import com.fasterxml.jackson.databind.util.TokenBuffer;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonHelper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.NonNull; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.Optional; import java.util.Optional;
@ -15,19 +16,20 @@ import java.util.Optional;
* *
* This instance is only valid in combination with its associated data store instance. * This instance is only valid in combination with its associated data store instance.
*/ */
@Data
@NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public abstract class DataSource<DS extends DataStore> { public abstract class DataSource<DS extends DataStore> {
@NonNull
protected DS store; protected DS store;
@SneakyThrows @SneakyThrows
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public DataSource<DS> copy() { public <T extends DataSource<DS>> T copy() {
var mapper = JacksonHelper.newMapper(); var mapper = JacksonHelper.newMapper();
TokenBuffer tb = new TokenBuffer(mapper, false); TokenBuffer tb = new TokenBuffer(mapper, false);
mapper.writeValue(tb, this); mapper.writeValue(tb, this);
return mapper.readValue(tb.asParser(), getClass()); return (T) mapper.readValue(tb.asParser(), getClass());
} }
public DataSource<DS> withStore(DS store) { public DataSource<DS> withStore(DS store) {
@ -36,6 +38,10 @@ public abstract class DataSource<DS extends DataStore> {
return c; return c;
} }
public boolean isComplete() {
return true;
}
/** /**
* Casts this instance to the required type without checking whether a cast is possible. * Casts this instance to the required type without checking whether a cast is possible.
*/ */
@ -63,6 +69,10 @@ public abstract class DataSource<DS extends DataStore> {
public abstract DataSourceConnection openWriteConnection() throws Exception; public abstract DataSourceConnection openWriteConnection() throws Exception;
public DataSourceConnection openAppendingWriteConnection() throws Exception {
throw new UnsupportedOperationException("Appending write is not supported");
}
public DS getStore() { public DS getStore() {
return store; return store;
} }

View file

@ -1,12 +1,13 @@
package io.xpipe.core.source; package io.xpipe.core.source;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import io.xpipe.core.config.ConfigOptionSet; import io.xpipe.core.config.ConfigParameter;
import io.xpipe.core.config.ConfigOptionSetInstance; import io.xpipe.core.config.ConfigParameterSetInstance;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
/** /**
* Represents the current configuration of a data source. * Represents the current configuration of a data source.
@ -17,7 +18,7 @@ import java.util.Map;
public class DataSourceConfigInstance { public class DataSourceConfigInstance {
public static DataSourceConfigInstance xpbt() { public static DataSourceConfigInstance xpbt() {
return new DataSourceConfigInstance("xpbt", ConfigOptionSet.empty(), Map.of()); return new DataSourceConfigInstance("xpbt", new ConfigParameterSetInstance(Map.of()));
} }
/** /**
@ -26,22 +27,21 @@ public class DataSourceConfigInstance {
String provider; String provider;
/** /**
* The available configuration options. * The available configuration parameters.
*/ */
ConfigOptionSet configOptions; ConfigParameterSetInstance configInstance;
/** public DataSourceConfigInstance(String provider, Map<ConfigParameter, Object> map) {
* The current configuration options that are set. this.provider = provider;
*/ this.configInstance = new ConfigParameterSetInstance(map);
Map<String, String> currentValues;
public boolean isComplete() {
return currentValues.size() == configOptions.getOptions().size();
} }
public DataSourceConfigInstance(String provider, ConfigOptionSetInstance cInstance) { public <X, T extends Function<X,?>> DataSourceConfigInstance(String provider, Map<ConfigParameter, T> map, Object val) {
this.provider = provider; this.provider = provider;
this.configOptions = cInstance.getConfigOptions(); this.configInstance = new ConfigParameterSetInstance(map, val);
this.currentValues = cInstance.getCurrentValues(); }
public Map<ConfigParameter, Object> evaluate() {
return configInstance.evaluate();
} }
} }

View file

@ -1,7 +1,13 @@
package io.xpipe.core.source; package io.xpipe.core.source;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public abstract class TableDataSource<DS extends DataStore> extends DataSource<DS> { public abstract class TableDataSource<DS extends DataStore> extends DataSource<DS> {
public TableDataSource(DS store) { public TableDataSource(DS store) {
@ -29,7 +35,15 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
return con; return con;
} }
public final TableWriteConnection openAppendingWriteConnection() throws Exception {
var con = newAppendingWriteConnection();
con.init();
return con;
}
protected abstract TableWriteConnection newWriteConnection(); protected abstract TableWriteConnection newWriteConnection();
protected abstract TableWriteConnection newAppendingWriteConnection();
protected abstract TableReadConnection newReadConnection(); protected abstract TableReadConnection newReadConnection();
} }

View file

@ -15,6 +15,30 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public interface TableReadConnection extends DataSourceReadConnection { public interface TableReadConnection extends DataSourceReadConnection {
public static TableReadConnection empty() {
return new TableReadConnection() {
@Override
public TupleType getDataType() throws Exception {
return TupleType.empty();
}
@Override
public int getRowCount() throws Exception {
return 0;
}
@Override
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
}
@Override
public ArrayNode readRows(int maxLines) throws Exception {
return ArrayNode.of();
}
};
}
/** /**
* Returns the data type of the table data. * Returns the data type of the table data.
*/ */
@ -53,9 +77,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
} }
default void forward(DataSourceConnection con) throws Exception { default void forward(DataSourceConnection con) throws Exception {
try (var tCon = (TableWriteConnection) con) { var tCon = (TableWriteConnection) con;
tCon.init(); withRows(tCon.writeLinesAcceptor());
withRows(tCon.writeLinesAcceptor());
}
} }
} }

View file

@ -1,9 +1,11 @@
package io.xpipe.core.source; package io.xpipe.core.source;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import lombok.NoArgsConstructor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@NoArgsConstructor
public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS> { public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS> {
private static final int MAX_LINE_READ = 1000; private static final int MAX_LINE_READ = 1000;

View file

@ -10,5 +10,11 @@ public interface FileDataStore extends StreamDataStore {
var i = n.lastIndexOf('.'); var i = n.lastIndexOf('.');
return Optional.of(i != -1 ? n.substring(0, i) : n); return Optional.of(i != -1 ? n.substring(0, i) : n);
} }
@Override
default boolean persistent() {
return true;
}
String getFileName(); String getFileName();
} }

View file

@ -1,6 +1,7 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -15,7 +16,6 @@ public class InputStreamDataStore implements StreamDataStore {
private final InputStream in; private final InputStream in;
private BufferedInputStream bufferedInputStream; private BufferedInputStream bufferedInputStream;
private boolean opened = false;
public InputStreamDataStore(InputStream in) { public InputStreamDataStore(InputStream in) {
this.in = in; this.in = in;
@ -23,13 +23,84 @@ public class InputStreamDataStore implements StreamDataStore {
@Override @Override
public InputStream openInput() throws Exception { public InputStream openInput() throws Exception {
if (opened) { if (bufferedInputStream != null) {
bufferedInputStream.reset();
return bufferedInputStream; return bufferedInputStream;
} }
opened = true;
bufferedInputStream = new BufferedInputStream(in); bufferedInputStream = new BufferedInputStream(in);
return bufferedInputStream; bufferedInputStream.mark(Integer.MAX_VALUE);
return new InputStream() {
@Override
public int read() throws IOException {
return bufferedInputStream.read();
}
@Override
public int read(byte[] b) throws IOException {
return bufferedInputStream.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return bufferedInputStream.read(b, off, len);
}
@Override
public byte[] readAllBytes() throws IOException {
return bufferedInputStream.readAllBytes();
}
@Override
public byte[] readNBytes(int len) throws IOException {
return bufferedInputStream.readNBytes(len);
}
@Override
public int readNBytes(byte[] b, int off, int len) throws IOException {
return bufferedInputStream.readNBytes(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return bufferedInputStream.skip(n);
}
@Override
public void skipNBytes(long n) throws IOException {
bufferedInputStream.skipNBytes(n);
}
@Override
public int available() throws IOException {
return bufferedInputStream.available();
}
@Override
public void close() throws IOException {
reset();
}
@Override
public synchronized void mark(int readlimit) {
bufferedInputStream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
bufferedInputStream.reset();
}
@Override
public boolean markSupported() {
return bufferedInputStream.markSupported();
}
@Override
public long transferTo(OutputStream out) throws IOException {
return bufferedInputStream.transferTo(out);
}
};
} }
@Override @Override

View file

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant; import java.time.Instant;
import java.util.Optional; import java.util.Optional;
@ -52,6 +53,11 @@ public class LocalFileDataStore implements FileDataStore {
return Files.newOutputStream(file); return Files.newOutputStream(file);
} }
@Override
public OutputStream openAppendingOutput() throws Exception {
return Files.newOutputStream(file, StandardOpenOption.APPEND);
}
@Override @Override
public boolean exists() { public boolean exists() {
return Files.exists(file); return Files.exists(file);

View file

@ -45,5 +45,13 @@ public interface StreamDataStore extends DataStore {
throw new UnsupportedOperationException("Can't open store output"); throw new UnsupportedOperationException("Can't open store output");
} }
default OutputStream openAppendingOutput() throws Exception {
throw new UnsupportedOperationException("Can't open store output");
}
boolean exists(); boolean exists();
default boolean persistent() {
return false;
}
} }

View file

@ -0,0 +1,22 @@
package io.xpipe.core.store;
import lombok.Value;
import java.io.InputStream;
import java.net.URL;
@Value
public class URLDataStore implements StreamDataStore {
URL url;
@Override
public InputStream openInput() throws Exception {
return url.openStream();
}
@Override
public boolean exists() {
return true;
}
}

View file

@ -18,6 +18,7 @@ module io.xpipe.core {
opens io.xpipe.core.data.typed; opens io.xpipe.core.data.typed;
exports io.xpipe.core.config; exports io.xpipe.core.config;
opens io.xpipe.core.config; opens io.xpipe.core.config;
exports io.xpipe.core.connection;
requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;

View file

@ -30,7 +30,9 @@ repositories {
} }
dependencies { dependencies {
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
implementation project(':core') implementation project(':core')
implementation project(':charsetter')
implementation 'io.xpipe:fxcomps:0.1' implementation 'io.xpipe:fxcomps:0.1'
implementation 'com.google.code.gson:gson:2.9.0' implementation 'com.google.code.gson:gson:2.9.0'

View file

@ -1,14 +1,16 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.config.ConfigOption; import io.xpipe.charsetter.NewLine;
import io.xpipe.core.config.ConfigOptionSet; import io.xpipe.core.config.ConfigConverter;
import io.xpipe.core.config.ConfigParameter;
import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import lombok.Value;
import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -26,9 +28,19 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return GeneralType.FILE; return GeneralType.FILE;
} }
if (getDatabaseProvider() != null) {
return GeneralType.DATABASE;
}
throw new ExtensionException("Provider has no general type"); throw new ExtensionException("Provider has no general type");
} }
@SneakyThrows
@SuppressWarnings("unchecked")
default T createDefault() {
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance();
}
default boolean supportsConversion(T in, DataSourceType t) { default boolean supportsConversion(T in, DataSourceType t) {
return false; return false;
} }
@ -37,7 +49,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
throw new ExtensionException(); throw new ExtensionException();
} }
default void init() { default void init() throws Exception {
} }
default String i18n(String key) { default String i18n(String key) {
@ -48,7 +60,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return getId() + "." + key; return getId() + "." + key;
} }
default Region createConfigOptions(DataStore input, Property<? extends DataSource<?>> source) { default Region createConfigGui(Property<T> source) {
return null; return null;
} }
@ -75,27 +87,32 @@ public interface DataSourceProvider<T extends DataSource<?>> {
Map<String, List<String>> getFileExtensions(); Map<String, List<String>> getFileExtensions();
} }
interface DatabaseProvider {
}
interface ConfigProvider<T extends DataSource<?>> { interface ConfigProvider<T extends DataSource<?>> {
@Value
static class Parameter {
ConfigParameter parameter;
Object currentValue;
boolean required;
}
static <T extends DataSource<?>> ConfigProvider<T> empty(List<String> names, Function<DataStore, T> func) { static <T extends DataSource<?>> ConfigProvider<T> empty(List<String> names, Function<DataStore, T> func) {
return new ConfigProvider<>() { return new ConfigProvider<>() {
@Override @Override
public ConfigOptionSet getConfig() { public void applyConfig(T source, Map<ConfigParameter, Object> values) {
return ConfigOptionSet.empty();
} }
@Override @Override
public T toDescriptor(DataStore store, Map<String, String> values) { public Map<ConfigParameter, Function<T, Object>> toCompleteConfig() {
return func.apply(store);
}
@Override
public Map<String, String> toConfigOptions(T source) {
return Map.of(); return Map.of();
} }
@Override @Override
public Map<ConfigOption, Function<String, ?>> getConverters() { public Map<ConfigParameter, Object> toRequiredReadConfig(T desc) {
return Map.of(); return Map.of();
} }
@ -106,52 +123,29 @@ public interface DataSourceProvider<T extends DataSource<?>> {
}; };
} }
ConfigOption ConfigParameter CHARSET = new ConfigParameter(
CHARSET_OPTION = new ConfigOption("Charset", "charset"); "charset", ConfigConverter.CHARSET);
Function<String, Charset>
CHARSET_CONVERTER = ConfigProvider.charsetConverter();
Function<Charset, String>
CHARSET_STRING = Charset::name;
static String booleanName(String name) { public static final ConfigConverter<NewLine> NEW_LINE_CONVERTER = new ConfigConverter<NewLine>() {
return name + " (y/n)"; @Override
} protected NewLine fromString(String s) {
return NewLine.id(s);
}
static Function<String, Boolean> booleanConverter() { @Override
return s -> { protected String toString(NewLine value) {
if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes")) { return value.getId();
return true; }
} };
if (s.equalsIgnoreCase("n") || s.equalsIgnoreCase("no")) { ConfigParameter NEWLINE = new ConfigParameter(
return false; "newline", NEW_LINE_CONVERTER);
}
throw new IllegalArgumentException("Invalid boolean: " + s); void applyConfig(T source, Map<ConfigParameter, Object> values);
};
}
static Function<String, Character> characterConverter() { Map<ConfigParameter, Function<T, Object>> toCompleteConfig();
return s -> {
if (s.length() != 1) {
throw new IllegalArgumentException("Invalid character: " + s);
}
return s.toCharArray()[0]; Map<ConfigParameter, Object> toRequiredReadConfig(T desc);
};
}
static Function<String, Charset> charsetConverter() {
return Charset::forName;
}
ConfigOptionSet getConfig();
T toDescriptor(DataStore store, Map<String, String> values);
Map<String, String> toConfigOptions(T desc);
Map<ConfigOption, Function<String, ?>> getConverters();
List<String> getPossibleNames(); List<String> getPossibleNames();
} }
@ -190,6 +184,10 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return null; return null;
} }
default DatabaseProvider getDatabaseProvider() {
return null;
}
default boolean hasDirectoryProvider() { default boolean hasDirectoryProvider() {
return false; return false;
} }
@ -206,16 +204,16 @@ public interface DataSourceProvider<T extends DataSource<?>> {
* Attempt to create a useful data source descriptor from a data store. * Attempt to create a useful data source descriptor from a data store.
* The result does not need to be always right, it should only reflect the best effort. * The result does not need to be always right, it should only reflect the best effort.
*/ */
T createDefaultDescriptor(DataStore input) throws Exception; T createDefaultSource(DataStore input) throws Exception;
default T createDefaultWriteDescriptor(DataStore input) throws Exception { default T createDefaultWriteSource(DataStore input) throws Exception {
return createDefaultDescriptor(input); return createDefaultSource(input);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default Class<T> getDescriptorClass() { default Class<T> getSourceClass() {
return (Class<T>) Arrays.stream(getClass().getDeclaredClasses()) return (Class<T>) Arrays.stream(getClass().getDeclaredClasses())
.filter(c -> c.getName().endsWith("Descriptor")).findFirst() .filter(c -> c.getName().endsWith("Source")).findFirst()
.orElseThrow(() -> new AssertionError("Descriptor class not found")); .orElseThrow(() -> new ExtensionException("Descriptor class not found for " + getId()));
} }
} }

View file

@ -3,6 +3,7 @@ package io.xpipe.extension;
import io.xpipe.core.source.*; import io.xpipe.core.source.*;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LocalFileDataStore; import io.xpipe.core.store.LocalFileDataStore;
import io.xpipe.extension.event.ErrorEvent;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.Optional; import java.util.Optional;
@ -18,7 +19,13 @@ public class DataSourceProviders {
if (ALL == null) { if (ALL == null) {
ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream() ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream()
.map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet()); .map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet());
ALL.forEach(DataSourceProvider::init); ALL.forEach(p -> {
try {
p.init();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
} }
} }
@ -41,7 +48,7 @@ public class DataSourceProviders {
@SneakyThrows @SneakyThrows
public static StructureDataSource<LocalFileDataStore> createLocalStructureDescriptor(DataStore store) { public static StructureDataSource<LocalFileDataStore> createLocalStructureDescriptor(DataStore store) {
return (StructureDataSource<LocalFileDataStore>) return (StructureDataSource<LocalFileDataStore>)
DataSourceProviders.byId("xpbs").getDescriptorClass() DataSourceProviders.byId("xpbs").getSourceClass()
.getDeclaredConstructors()[0].newInstance(store); .getDeclaredConstructors()[0].newInstance(store);
} }
@ -49,7 +56,7 @@ public class DataSourceProviders {
@SneakyThrows @SneakyThrows
public static RawDataSource<LocalFileDataStore> createLocalRawDescriptor(DataStore store) { public static RawDataSource<LocalFileDataStore> createLocalRawDescriptor(DataStore store) {
return (RawDataSource<LocalFileDataStore>) return (RawDataSource<LocalFileDataStore>)
DataSourceProviders.byId("binary").getDescriptorClass() DataSourceProviders.byId("binary").getSourceClass()
.getDeclaredConstructors()[0].newInstance(store); .getDeclaredConstructors()[0].newInstance(store);
} }
@ -57,7 +64,7 @@ public class DataSourceProviders {
@SneakyThrows @SneakyThrows
public static RawDataSource<LocalFileDataStore> createLocalCollectionDescriptor(DataStore store) { public static RawDataSource<LocalFileDataStore> createLocalCollectionDescriptor(DataStore store) {
return (RawDataSource<LocalFileDataStore>) return (RawDataSource<LocalFileDataStore>)
DataSourceProviders.byId("br").getDescriptorClass() DataSourceProviders.byId("br").getSourceClass()
.getDeclaredConstructors()[0].newInstance(store); .getDeclaredConstructors()[0].newInstance(store);
} }
@ -65,7 +72,7 @@ public class DataSourceProviders {
@SneakyThrows @SneakyThrows
public static TextDataSource<LocalFileDataStore> createLocalTextDescriptor(DataStore store) { public static TextDataSource<LocalFileDataStore> createLocalTextDescriptor(DataStore store) {
return (TextDataSource<LocalFileDataStore>) return (TextDataSource<LocalFileDataStore>)
DataSourceProviders.byId("text").getDescriptorClass() DataSourceProviders.byId("text").getSourceClass()
.getDeclaredConstructors()[0].newInstance(store); .getDeclaredConstructors()[0].newInstance(store);
} }
@ -73,7 +80,7 @@ public class DataSourceProviders {
@SneakyThrows @SneakyThrows
public static TableDataSource<LocalFileDataStore> createLocalTableDescriptor(DataStore store) { public static TableDataSource<LocalFileDataStore> createLocalTableDescriptor(DataStore store) {
return (TableDataSource<LocalFileDataStore>) return (TableDataSource<LocalFileDataStore>)
DataSourceProviders.byId("xpbt").getDescriptorClass() DataSourceProviders.byId("xpbt").getSourceClass()
.getDeclaredConstructors()[0].newInstance(store); .getDeclaredConstructors()[0].newInstance(store);
} }
@ -94,7 +101,7 @@ public class DataSourceProviders {
throw new IllegalStateException("Not initialized"); throw new IllegalStateException("Not initialized");
} }
return (T) ALL.stream().filter(d -> d.getDescriptorClass().equals(c)).findAny() return (T) ALL.stream().filter(d -> d.getSourceClass().equals(c)).findAny()
.orElseThrow(() -> new IllegalArgumentException("Provider for " + c.getSimpleName() + " not found")); .orElseThrow(() -> new IllegalArgumentException("Provider for " + c.getSimpleName() + " not found"));
} }

View file

@ -0,0 +1,36 @@
package io.xpipe.extension;
import io.xpipe.charsetter.Charsetter;
import io.xpipe.charsetter.CharsetterContext;
import io.xpipe.core.util.JacksonHelper;
import io.xpipe.extension.util.ModuleHelper;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
public class ExtensionTest {
@BeforeAll
public static void setup() throws Exception {
var mod = ModuleLayer.boot().modules().stream()
.filter(m -> m.getName().contains(".test"))
.findFirst().orElseThrow();
var e = ModuleHelper.getEveryoneModule();
for (var pkg : mod.getPackages()) {
ModuleHelper.exportAndOpen(pkg, e);
}
var extMod = ModuleLayer.boot().modules().stream()
.filter(m -> m.getName().equals(mod.getName().replace(".test", "")))
.findFirst().orElseThrow();
for (var pkg : extMod.getPackages()) {
ModuleHelper.exportAndOpen(pkg, e);
}
JacksonHelper.initClassBased();
Charsetter.init(CharsetterContext.empty());
}
@AfterAll
public static void teardown() throws Exception {
}
}

View file

@ -10,14 +10,14 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
@Override @Override
default ConfigProvider<T> getConfigProvider() { default ConfigProvider<T> getConfigProvider() {
return ConfigProvider.empty(List.of(getId()), this::createDefaultDescriptor); return ConfigProvider.empty(List.of(getId()), this::createDefaultSource);
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default T createDefaultDescriptor(DataStore input) { default T createDefaultSource(DataStore input) {
try { try {
return (T) getDescriptorClass().getDeclaredConstructors()[0].newInstance(input); return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

View file

@ -4,21 +4,20 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.util.function.Supplier;
public class CharChoiceComp extends Comp<CompStructure<HBox>> { public class CharChoiceComp extends Comp<CompStructure<HBox>> {
private final Property<Character> value; private final Property<Character> value;
private final Property<Character> charChoiceValue; private final Property<Character> charChoiceValue;
private final BidiMap<Character, Supplier<String>> range; private final BidiMap<Character, ObservableValue<String>> range;
private final Supplier<String> customName; private final ObservableValue<String> customName;
public CharChoiceComp(Property<Character> value, BidiMap<Character, Supplier<String>> range, Supplier<String> customName) { public CharChoiceComp(Property<Character> value, BidiMap<Character, ObservableValue<String>> range, ObservableValue<String> customName) {
this.value = value; this.value = value;
this.range = range; this.range = range;
this.customName = customName; this.customName = customName;

View file

@ -4,12 +4,13 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.comp.ReplacementComp; import io.xpipe.fxcomps.comp.ReplacementComp;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.function.Supplier;
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> { public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
@ -21,9 +22,9 @@ public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Ch
@Override @Override
protected Comp<CompStructure<ComboBox<Charset>>> createComp() { protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
var map = new LinkedHashMap<Charset, Supplier<String>>(); var map = new LinkedHashMap<Charset, ObservableValue<String>>();
for (var e : Charset.availableCharsets().entrySet()) { for (var e : Charset.availableCharsets().entrySet()) {
map.put(e.getValue(), e::getKey); map.put(e.getValue(), new SimpleStringProperty(e.getKey()));
} }
return new ChoiceComp<>(charset, new DualLinkedHashBidiMap<>(map)); return new ChoiceComp<>(charset, new DualLinkedHashBidiMap<>(map));
} }

View file

@ -4,19 +4,18 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.util.PlatformUtil; import io.xpipe.fxcomps.util.PlatformUtil;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.BidiMap;
import java.util.function.Supplier;
public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> { public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
private final Property<T> value; private final Property<T> value;
private final BidiMap<T, Supplier<String>> range; private final BidiMap<T, ObservableValue<String>> range;
public ChoiceComp(Property<T> value, BidiMap<T, Supplier<String>> range) { public ChoiceComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) {
this.value = value; this.value = value;
this.range = range; this.range = range;
} }
@ -28,7 +27,11 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
cb.setConverter(new StringConverter<>() { cb.setConverter(new StringConverter<>() {
@Override @Override
public String toString(T object) { public String toString(T object) {
return range.get(object).get(); if (object == null) {
return "null";
}
return range.get(object).getValue();
} }
@Override @Override

View file

@ -1,17 +1,24 @@
package io.xpipe.extension.comp; package io.xpipe.extension.comp;
import io.xpipe.charsetter.NewLine;
import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSource;
import io.xpipe.extension.I18n;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.Map;
import java.util.function.Function;
public class DynamicOptionsBuilder<T extends DataSource<?>> { public class DynamicOptionsBuilder<T extends DataSource<?>> {
@ -22,12 +29,54 @@ public class DynamicOptionsBuilder<T extends DataSource<?>> {
var comp = new TextField(); var comp = new TextField();
comp.textProperty().bindBidirectional(prop); comp.textProperty().bindBidirectional(prop);
entries.add(new DynamicOptionsComp.Entry(name, Comp.of(() -> comp))); entries.add(new DynamicOptionsComp.Entry(name, Comp.of(() -> comp)));
props.add(prop);
return this; return this;
} }
public Region build(Supplier<T> creator, Property<T> toBind) { public DynamicOptionsBuilder<T> addNewLine(Property<NewLine> prop) {
var bind = Bindings.createObjectBinding(() -> creator.get(), props.toArray(Observable[]::new)); var map = new LinkedHashMap<NewLine, ObservableValue<String>>();
toBind.bind(bind); for (var e : NewLine.values()) {
map.put(e, new SimpleStringProperty(e.getId()));
}
var comp = new ChoiceComp<>(prop, new DualLinkedHashBidiMap<>(map));
entries.add(new DynamicOptionsComp.Entry(I18n.observable("newLine"), comp));
props.add(prop);
return this;
}
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names) {
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), null);
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);
return this;
}
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names, ObservableValue<String> customName) {
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), customName);
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);
return this;
}
public <V> DynamicOptionsBuilder<T> addToggle(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
var comp = new ToggleGroupComp<>(prop, new DualLinkedHashBidiMap<>(names));
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);
return this;
}
public DynamicOptionsBuilder<T> addCharset(Property<Charset> prop) {
var comp = new CharsetChoiceComp(prop);
entries.add(new DynamicOptionsComp.Entry(I18n.observable("charset"), comp));
props.add(prop);
return this;
}
public Region build(Function<T, T> creator, Property<T> toBind) {
var bind = Bindings.createObjectBinding(() -> creator.apply(toBind.getValue()), props.toArray(Observable[]::new));
bind.addListener((c,o, n) -> {
toBind.setValue(n);
});
return new DynamicOptionsComp(entries).createRegion(); return new DynamicOptionsComp(entries).createRegion();
} }
} }

View file

@ -4,24 +4,23 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.util.PlatformUtil; import io.xpipe.fxcomps.util.PlatformUtil;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.BidiMap;
import java.util.function.Supplier;
public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> { public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
private final Property<T> value; private final Property<T> value;
private final BidiMap<T, Supplier<String>> range; private final BidiMap<T, ObservableValue<String>> range;
public ToggleGroupComp(Property<T> value, BidiMap<T, Supplier<String>> range) { public ToggleGroupComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) {
this.value = value; this.value = value;
this.range = range; this.range = range;
} }
public BidiMap<T, Supplier<String>> getRange() { public BidiMap<T, ObservableValue<String>> getRange() {
return range; return range;
} }
@ -31,7 +30,7 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
box.getStyleClass().add("toggle-group-comp"); box.getStyleClass().add("toggle-group-comp");
ToggleGroup group = new ToggleGroup(); ToggleGroup group = new ToggleGroup();
for (var entry : range.entrySet()) { for (var entry : range.entrySet()) {
var b = new ToggleButton(entry.getValue().get()); var b = new ToggleButton(entry.getValue().getValue());
b.setOnAction(e -> { b.setOnAction(e -> {
value.setValue(entry.getKey()); value.setValue(entry.getKey());
e.consume(); e.consume();

View file

@ -15,7 +15,11 @@ public abstract class EventHandler {
@Override @Override
public void handle(TrackEvent te) { public void handle(TrackEvent te) {
LoggerFactory.getLogger(te.getCategory()).info(te.getMessage()); var cat = te.getCategory();
if (cat == null) {
cat = "log";
}
LoggerFactory.getLogger(cat).info(te.getMessage());
} }
@Override @Override

View file

@ -0,0 +1,58 @@
package io.xpipe.extension.util;
import lombok.SneakyThrows;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ModuleHelper {
public static void exportAndOpen(String modName) {
var mod = ModuleLayer.boot().modules().stream()
.filter(m -> m.getName().equalsIgnoreCase(modName))
.findFirst().orElseThrow();
var e = getEveryoneModule();
for (var pkg : mod.getPackages()) {
exportAndOpen(pkg, e);
}
}
@SneakyThrows
public static Module getEveryoneModule() {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Module.class, false);
Field modifiers = null;
for (Field each : fields) {
if ("EVERYONE_MODULE".equals(each.getName())) {
modifiers = each;
break;
}
}
modifiers.setAccessible(true);
return (Module) modifiers.get(null);
}
@SneakyThrows
public static void exportAndOpen(String pkg, Module mod) {
if (mod.isExported(pkg) && mod.isOpen(pkg)) {
return;
}
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredMethods0", boolean.class);
getDeclaredFields0.setAccessible(true);
Method[] fields = (Method[]) getDeclaredFields0.invoke(Module.class, false);
Method modifiers = null;
for (Method each : fields) {
if ("implAddExportsOrOpens".equals(each.getName())) {
modifiers = each;
break;
}
}
modifiers.setAccessible(true);
var e = getEveryoneModule();
modifiers.invoke(mod, pkg, e, false, true);
modifiers.invoke(mod, pkg, e, true, true);
}
}

View file

@ -27,6 +27,8 @@ module io.xpipe.extension {
requires org.reactfx; requires org.reactfx;
requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.javafx;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;
requires static org.junit.jupiter.api;
requires io.xpipe.charsetter;
uses DataSourceProvider; uses DataSourceProvider;
uses SupportedApplicationProvider; uses SupportedApplicationProvider;