Add more data sources and refactor some parts

This commit is contained in:
Christopher Schnick 2022-03-03 16:44:14 +01:00
parent bcc581c0bd
commit 2c041ecb0e
42 changed files with 775 additions and 196 deletions

View file

@ -0,0 +1,16 @@
package io.xpipe.api;
import io.xpipe.core.source.DataSourceInfo;
import java.io.InputStream;
public interface DataRaw extends DataSource {
DataSourceInfo.Raw getInfo();
InputStream open();
byte[] readAll();
byte[] read(int maxBytes);
}

View file

@ -3,6 +3,7 @@ package io.xpipe.api;
import io.xpipe.api.impl.DataSourceImpl; import io.xpipe.api.impl.DataSourceImpl;
import io.xpipe.core.source.DataSourceConfig; import io.xpipe.core.source.DataSourceConfig;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
import java.io.InputStream; import java.io.InputStream;
@ -10,14 +11,22 @@ import java.net.URL;
import java.util.Map; import java.util.Map;
/** /**
* Represents a reference to an XPipe data source. * Represents a reference to a data source that is managed by X-Pipe.
* *
* The actual data is only queried when required and is not cached. * The actual data is only queried when required and is not cached.
* Therefore, the queried data is always up-to-date at the point of calling a method that queries the data. * Therefore, the queried data is always up-to-date at the point of calling a method that queries the data.
*
* As soon a data source reference is created, the data source is locked
* within X-Pipe to prevent concurrent modification and the problems that can arise from it.
* By default, the lock is held until the calling program terminates and prevents
* other applications from modifying the data source in any way.
* To unlock the data source earlier, you can make use the {@link #unlock()} method.
*/ */
public interface DataSource { public interface DataSource {
/** /**
* NOT YET IMPLEMENTED!
*
* Creates a new supplier data source that will be interpreted as the generated data source. * Creates a new supplier data source that will be interpreted as the generated data source.
* In case this program should be a data source generator, this method has to be called at * In case this program should be a data source generator, this method has to be called at
* least once to register that it actually generates a data source. * least once to register that it actually generates a data source.
@ -29,28 +38,49 @@ public interface DataSource {
* *
* @return the generator data source * @return the generator data source
*/ */
@Deprecated
static DataSource supplySource() { static DataSource supplySource() {
return null; return null;
} }
/** /**
* Wrapper for {@link #get(DataSourceId)}. * Wrapper for {@link #get(DataSourceReference)}.
* *
* @throws IllegalArgumentException if {@code id} is not a valid data source id * @throws IllegalArgumentException if {@code id} is not a valid data source id
*/ */
static DataSource get(String id) { static DataSource getById(String id) {
return get(DataSourceId.fromString(id)); return get(DataSourceReference.id(id));
}
/**
* Wrapper for {@link #get(DataSourceReference)} using the latest reference.
*/
static DataSource getLatest() {
return get(DataSourceReference.latest());
}
/**
* Wrapper for {@link #get(DataSourceReference)} using a name reference.
*/
static DataSource getByName(String name) {
return get(DataSourceReference.name(name));
} }
/** /**
* Retrieves a reference to the given data source. * Retrieves a reference to the given data source.
* *
* @param id the data source id * @param ref the data source reference
* @return a reference to the data source that can be used to access the underlying data source
*/ */
static DataSource get(DataSourceId id) { static DataSource get(DataSourceReference ref) {
return null; return DataSourceImpl.get(ref);
//return DataSourceImpl.get(id); }
/**
* Releases the lock held by this program for this data source such
* that other applications can modify the data source again.
*/
static void unlock() {
throw new UnsupportedOperationException();
} }
static DataSource wrap(InputStream in, String type, Map<String, String> configOptions) { static DataSource wrap(InputStream in, String type, Map<String, String> configOptions) {
@ -110,4 +140,31 @@ public interface DataSource {
default DataTable asTable() { default DataTable asTable() {
throw new UnsupportedOperationException("Data source is not a table"); throw new UnsupportedOperationException("Data source is not a table");
} }
/**
* Attempts to cast this object to a {@link DataStructure}.
*
* @throws UnsupportedOperationException if the data source is not a structure
*/
default DataStructure asStructure() {
throw new UnsupportedOperationException("Data source is not a structure");
}
/**
* Attempts to cast this object to a {@link DataText}.
*
* @throws UnsupportedOperationException if the data source is not a text
*/
default DataText asText() {
throw new UnsupportedOperationException("Data source is not a text");
}
/**
* Attempts to cast this object to a {@link DataRaw}.
*
* @throws UnsupportedOperationException if the data source is not raw
*/
default DataRaw asRaw() {
throw new UnsupportedOperationException("Data source is not raw");
}
} }

View file

@ -0,0 +1,11 @@
package io.xpipe.api;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.source.DataSourceInfo;
public interface DataStructure extends DataSource {
DataSourceInfo.Structure getInfo();
DataStructureNode read();
}

View file

@ -2,28 +2,16 @@ package io.xpipe.api;
import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.type.TupleType; import io.xpipe.core.source.DataSourceInfo;
import java.util.OptionalInt;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface DataTable extends Iterable<TupleNode>, DataSource { public interface DataTable extends Iterable<TupleNode>, DataSource {
/** DataSourceInfo.Table getInfo();
* @see DataSource#supplySource()
*/
static DataTable supplySource() {
return null;
}
Stream<TupleNode> stream(); Stream<TupleNode> stream();
int getRowCount();
OptionalInt getRowCountIfPresent();
TupleType getDataType();
ArrayNode readAll(); ArrayNode readAll();
ArrayNode read(int maxRows); ArrayNode read(int maxRows);

View file

@ -0,0 +1,28 @@
package io.xpipe.api;
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.source.DataSourceId;
/**
* An accumulator for table data.
*
* This class can be used to construct new table data sources by
* accumulating the rows using {@link #add(TupleNode)} or {@link #acceptor()} and then calling
* {@link #finish(DataSourceId)} to complete the construction process and create a new data source.
*/
public interface DataTableAccumulator {
/**
* Finishes the construction process and returns the data source reference.
*
* @param id the data source id to assign
*/
DataTable finish(DataSourceId id);
void add(TupleNode row);
DataStructureNodeAcceptor<TupleNode> acceptor();
int getCurrentRows();
}

View file

@ -0,0 +1,18 @@
package io.xpipe.api;
import io.xpipe.core.source.DataSourceInfo;
import java.util.List;
public interface DataText extends DataSource, Iterable<String> {
DataSourceInfo.Text getInfo();
List<String> readAllLines();
List<String> readLines(int maxLines);
String readAll();
String read(int maxCharacters);
}

View file

@ -1,19 +0,0 @@
package io.xpipe.api;
public class XPipeClientException extends RuntimeException {
public XPipeClientException() {
}
public XPipeClientException(String message) {
super(message);
}
public XPipeClientException(String message, Throwable cause) {
super(message, cause);
}
public XPipeClientException(Throwable cause) {
super(cause);
}
}

View file

@ -1,19 +0,0 @@
package io.xpipe.api;
public class XPipeConnectException extends RuntimeException {
public XPipeConnectException() {
}
public XPipeConnectException(String message) {
super(message);
}
public XPipeConnectException(String message, Throwable cause) {
super(message, cause);
}
public XPipeConnectException(Throwable cause) {
super(cause);
}
}

View file

@ -1,8 +0,0 @@
package io.xpipe.api;
import io.xpipe.core.data.node.DataStructureNode;
public interface XPipeDataStructureSource {
DataStructureNode read();
}

View file

@ -1,12 +0,0 @@
package io.xpipe.api;
import io.xpipe.core.source.DataSourceId;
public abstract class XPipeDataTableBuilder {
private DataSourceId id;
public abstract void write();
public abstract void commit();
}

View file

@ -1,23 +0,0 @@
package io.xpipe.api;
public class XPipeException extends RuntimeException {
public XPipeException() {
}
public XPipeException(String message) {
super(message);
}
public XPipeException(String message, Throwable cause) {
super(message, cause);
}
public XPipeException(Throwable cause) {
super(cause);
}
public XPipeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,19 +0,0 @@
package io.xpipe.api;
public class XPipeServerException extends RuntimeException {
public XPipeServerException() {
}
public XPipeServerException(String message) {
super(message);
}
public XPipeServerException(String message, Throwable cause) {
super(message, cause);
}
public XPipeServerException(Throwable cause) {
super(cause);
}
}

View file

@ -1,4 +1,4 @@
package io.xpipe.api; package io.xpipe.api.connector;
import io.xpipe.beacon.*; import io.xpipe.beacon.*;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonHelper;
@ -12,7 +12,7 @@ public abstract class XPipeApiConnector extends BeaconConnector {
var socket = constructSocket(); var socket = constructSocket();
handle(socket); handle(socket);
} catch (Throwable ce) { } catch (Throwable ce) {
throw new XPipeException(ce); throw new RuntimeException(ce);
} }
} }

View file

@ -0,0 +1,49 @@
package io.xpipe.api.impl;
import io.xpipe.api.DataRaw;
import io.xpipe.core.source.DataSourceConfig;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceType;
import java.io.InputStream;
public class DataRawImpl extends DataSourceImpl implements DataRaw {
private final DataSourceInfo.Raw info;
public DataRawImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Raw info) {
super(sourceId, sourceConfig);
this.info = info;
}
@Override
public DataSourceInfo.Raw getInfo() {
return info;
}
@Override
public InputStream open() {
return null;
}
@Override
public byte[] readAll() {
return new byte[0];
}
@Override
public byte[] read(int maxBytes) {
return new byte[0];
}
@Override
public DataSourceType getType() {
return DataSourceType.RAW;
}
@Override
public DataRaw asRaw() {
return this;
}
}

View file

@ -1,12 +1,12 @@
package io.xpipe.api.impl; package io.xpipe.api.impl;
import io.xpipe.api.DataSource; import io.xpipe.api.DataSource;
import io.xpipe.api.XPipeApiConnector; import io.xpipe.api.connector.XPipeApiConnector;
import io.xpipe.beacon.BeaconClient; import io.xpipe.beacon.BeaconClient;
import io.xpipe.beacon.ClientException; import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.ConnectorException; import io.xpipe.beacon.ConnectorException;
import io.xpipe.beacon.ServerException; import io.xpipe.beacon.ServerException;
import io.xpipe.beacon.exchange.InfoExchange; import io.xpipe.beacon.exchange.QueryDataSourceExchange;
import io.xpipe.beacon.exchange.StoreResourceExchange; import io.xpipe.beacon.exchange.StoreResourceExchange;
import io.xpipe.beacon.exchange.StoreStreamExchange; import io.xpipe.beacon.exchange.StoreStreamExchange;
import io.xpipe.core.source.DataSourceConfig; import io.xpipe.core.source.DataSourceConfig;
@ -24,9 +24,26 @@ public abstract class DataSourceImpl implements DataSource {
new XPipeApiConnector() { new XPipeApiConnector() {
@Override @Override
protected void handle(BeaconClient sc) throws ClientException, ServerException, ConnectorException { protected void handle(BeaconClient sc) throws ClientException, ServerException, ConnectorException {
var req = InfoExchange.Request.builder().ref(ds).build(); var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
InfoExchange.Response res = performSimpleExchange(sc, req); QueryDataSourceExchange.Response res = performSimpleExchange(sc, req);
switch (res.getInfo().getType()) {
case TABLE -> {
var data = res.getInfo().asTable();
source[0] = new DataTableImpl(res.getId(), res.getConfig().getConfig(), data);
}
case STRUCTURE -> {
var info = res.getInfo().asStructure();
source[0] = new DataStructureImpl(res.getId(), res.getConfig().getConfig(), info);
}
case TEXT -> {
var info = res.getInfo().asText();
source[0] = new DataTextImpl(res.getId(), res.getConfig().getConfig(), info);
}
case RAW -> {
var info = res.getInfo().asRaw();
source[0] = new DataRawImpl(res.getId(), res.getConfig().getConfig(), info);
}
}
} }
}.execute(); }.execute();
return source[0]; return source[0];

View file

@ -0,0 +1,38 @@
package io.xpipe.api.impl;
import io.xpipe.api.DataStructure;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.source.DataSourceConfig;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceType;
public class DataStructureImpl extends DataSourceImpl implements DataStructure {
private final DataSourceInfo.Structure info;
public DataStructureImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Structure info) {
super(sourceId, sourceConfig);
this.info = info;
}
@Override
public DataSourceType getType() {
return DataSourceType.STRUCTURE;
}
@Override
public DataStructure asStructure() {
return this;
}
@Override
public DataSourceInfo.Structure getInfo() {
return info;
}
@Override
public DataStructureNode read() {
return null;
}
}

View file

@ -0,0 +1,30 @@
package io.xpipe.api.impl;
import io.xpipe.api.DataTable;
import io.xpipe.api.DataTableAccumulator;
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.source.DataSourceId;
public class DataTableAccumulatorImpl implements DataTableAccumulator {
@Override
public DataTable finish(DataSourceId id) {
return null;
}
@Override
public void add(TupleNode row) {
}
@Override
public DataStructureNodeAcceptor<TupleNode> acceptor() {
return null;
}
@Override
public int getCurrentRows() {
return 0;
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.api.impl; package io.xpipe.api.impl;
import io.xpipe.api.DataTable; import io.xpipe.api.DataTable;
import io.xpipe.api.XPipeApiConnector; import io.xpipe.api.connector.XPipeApiConnector;
import io.xpipe.beacon.BeaconClient; import io.xpipe.beacon.BeaconClient;
import io.xpipe.beacon.ClientException; import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.ConnectorException; import io.xpipe.beacon.ConnectorException;
@ -9,7 +9,6 @@ import io.xpipe.beacon.ServerException;
import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.typed.TypedAbstractReader; import io.xpipe.core.data.typed.TypedAbstractReader;
import io.xpipe.core.data.typed.TypedDataStreamParser; import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader; import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader;
@ -27,12 +26,10 @@ import java.util.stream.StreamSupport;
public class DataTableImpl extends DataSourceImpl implements DataTable { public class DataTableImpl extends DataSourceImpl implements DataTable {
private final DataSourceId id;
private final DataSourceInfo.Table info; private final DataSourceInfo.Table info;
public DataTableImpl(DataSourceId id, DataSourceConfig sourceConfig, DataSourceInfo.Table info) { DataTableImpl(DataSourceId id, DataSourceConfig sourceConfig, DataSourceInfo.Table info) {
super(id, sourceConfig); super(id, sourceConfig);
this.id = id;
this.info = info; this.info = info;
} }
@ -41,40 +38,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
return this; return this;
} }
@Override
public DataSourceInfo.Table getInfo() {
return info;
}
public Stream<TupleNode> stream() { public Stream<TupleNode> stream() {
return StreamSupport.stream( return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false); Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false);
} }
@Override
public DataSourceId getId() {
return id;
}
@Override @Override
public DataSourceType getType() { public DataSourceType getType() {
return DataSourceType.TABLE; return DataSourceType.TABLE;
} }
@Override
public int getRowCount() {
if (info.getRowCount() == -1) {
throw new UnsupportedOperationException("Row count is unknown");
}
return info.getRowCount();
}
@Override
public OptionalInt getRowCountIfPresent() {
return info.getRowCount() != -1 ? OptionalInt.of(info.getRowCount()) : OptionalInt.empty();
}
@Override
public TupleType getDataType() {
return info.getDataType();
}
@Override @Override
public ArrayNode readAll() { public ArrayNode readAll() {
return read(Integer.MAX_VALUE); return read(Integer.MAX_VALUE);

View file

@ -0,0 +1,60 @@
package io.xpipe.api.impl;
import io.xpipe.api.DataText;
import io.xpipe.core.source.DataSourceConfig;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceType;
import java.util.Iterator;
import java.util.List;
public class DataTextImpl extends DataSourceImpl implements DataText {
private final DataSourceInfo.Text info;
public DataTextImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Text info) {
super(sourceId, sourceConfig);
this.info = info;
}
@Override
public DataSourceType getType() {
return DataSourceType.TEXT;
}
@Override
public DataText asText() {
return this;
}
@Override
public DataSourceInfo.Text getInfo() {
return info;
}
@Override
public List<String> readAllLines() {
return null;
}
@Override
public List<String> readLines(int maxLines) {
return null;
}
@Override
public String readAll() {
return null;
}
@Override
public String read(int maxCharacters) {
return null;
}
@Override
public Iterator<String> iterator() {
return null;
}
}

View file

@ -3,4 +3,5 @@ module io.xpipe.api {
requires io.xpipe.beacon; requires io.xpipe.beacon;
exports io.xpipe.api; exports io.xpipe.api;
exports io.xpipe.api.connector;
} }

View file

@ -16,8 +16,8 @@ public class BeaconConfig {
private static final String BEACON_PORT_PROP = "io.xpipe.beacon.port"; public static final String BEACON_PORT_PROP = "io.xpipe.beacon.port";
private static final int DEFAULT_PORT = 21721; public static final int DEFAULT_PORT = 21721;
public static int getUsedPort() { public static int getUsedPort() {
if (System.getProperty(BEACON_PORT_PROP) != null) { if (System.getProperty(BEACON_PORT_PROP) != null) {

View file

@ -2,30 +2,28 @@ package io.xpipe.beacon.exchange;
import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.RequestMessage;
import io.xpipe.beacon.message.ResponseMessage; import io.xpipe.beacon.message.ResponseMessage;
import io.xpipe.core.source.DataSourceConfigInstance; import io.xpipe.core.source.*;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
public class InfoExchange implements MessageExchange<InfoExchange.Request, InfoExchange.Response> { public class QueryDataSourceExchange implements MessageExchange<QueryDataSourceExchange.Request, QueryDataSourceExchange.Response> {
@Override @Override
public String getId() { public String getId() {
return "info"; return "queryDataSource";
} }
@Override @Override
public Class<InfoExchange.Request> getRequestClass() { public Class<QueryDataSourceExchange.Request> getRequestClass() {
return InfoExchange.Request.class; return QueryDataSourceExchange.Request.class;
} }
@Override @Override
public Class<InfoExchange.Response> getResponseClass() { public Class<QueryDataSourceExchange.Response> getResponseClass() {
return InfoExchange.Response.class; return QueryDataSourceExchange.Response.class;
} }
@Jacksonized @Jacksonized
@ -40,11 +38,15 @@ public class InfoExchange implements MessageExchange<InfoExchange.Request, InfoE
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
@NonNull
DataSourceId id;
@NonNull @NonNull
DataSourceInfo info; DataSourceInfo info;
@NonNull @NonNull
DataStore store; DataStore store;
@NonNull @NonNull
DataSourceDescriptor<?> descriptor;
@NonNull
DataSourceConfigInstance config; DataSourceConfigInstance config;
} }
} }

View file

@ -31,7 +31,7 @@ module io.xpipe.beacon {
ReadPreparationExchange, ReadPreparationExchange,
ReadExecuteExchange, ReadExecuteExchange,
DialogExchange, DialogExchange,
InfoExchange, QueryDataSourceExchange,
PreStoreExchange, PreStoreExchange,
EditPreparationExchange, EditPreparationExchange,
EditExecuteExchange, EditExecuteExchange,

View file

@ -13,7 +13,6 @@ import java.util.List;
@Builder @Builder
@Jacksonized @Jacksonized
public class DataSourceConfig { public class DataSourceConfig {
String description;
@Singular @Singular
List<Option> options; List<Option> options;

View file

@ -7,6 +7,10 @@ import io.xpipe.core.data.type.TupleType;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.OptionalInt;
/** /**
* A data source info instances contains all required * A data source info instances contains all required
* essential information of a specific data source type. * essential information of a specific data source type.
@ -34,12 +38,86 @@ public abstract class DataSourceInfo {
this.rowCount = rowCount; this.rowCount = rowCount;
} }
public OptionalInt getRowCountIfPresent() {
return getRowCount() != -1 ? OptionalInt.of(getRowCount()) : OptionalInt.empty();
}
@Override @Override
public DataSourceType getType() { public DataSourceType getType() {
return DataSourceType.TABLE; return DataSourceType.TABLE;
} }
} }
@EqualsAndHashCode(callSuper = false)
@Value
@JsonTypeName("structure")
public static class Structure extends DataSourceInfo {
@JsonCreator
public Structure() {
}
@Override
public DataSourceType getType() {
return DataSourceType.STRUCTURE;
}
}
@EqualsAndHashCode(callSuper = false)
@Value
@JsonTypeName("text")
public static class Text extends DataSourceInfo {
Charset encoding;
@JsonCreator
public Text(Charset encoding) {
this.encoding = encoding;
}
@Override
public DataSourceType getType() {
return DataSourceType.TEXT;
}
}
@EqualsAndHashCode(callSuper = false)
@Value
@JsonTypeName("raw")
public static class Raw extends DataSourceInfo {
int byteCount;
ByteOrder byteOrder;
@JsonCreator
public Raw(int byteCount, ByteOrder byteOrder) {
this.byteCount = byteCount;
this.byteOrder = byteOrder;
}
@Override
public DataSourceType getType() {
return DataSourceType.RAW;
}
}
@EqualsAndHashCode(callSuper = false)
@Value
@JsonTypeName("archive")
public static class Archive extends DataSourceInfo {
int contentCount;
@JsonCreator
public Archive(int contentCount) {
this.contentCount = contentCount;
}
@Override
public DataSourceType getType() {
return null;
}
}
/** /**
* Casts this instance to a table info. * Casts this instance to a table info.
*/ */
@ -50,4 +128,37 @@ public abstract class DataSourceInfo {
return (Table) this; return (Table) this;
} }
/**
* Casts this instance to a structure info.
*/
public Structure asStructure() {
if (!getType().equals(DataSourceType.STRUCTURE)) {
throw new IllegalStateException("Not a structure");
}
return (Structure) this;
}
/**
* Casts this instance to a text info.
*/
public Text asText() {
if (!getType().equals(DataSourceType.TEXT)) {
throw new IllegalStateException("Not a text");
}
return (Text) this;
}
/**
* Casts this instance to a raw info.
*/
public Raw asRaw() {
if (!getType().equals(DataSourceType.RAW)) {
throw new IllegalStateException("Not raw");
}
return (Raw) this;
}
} }

View file

@ -6,15 +6,56 @@ import lombok.Value;
import java.util.Objects; import java.util.Objects;
/**
* Represents a reference to an X-Pipe data source.
* Using {@link DataSourceReference} instances instead of {@link DataSourceId}
* instances is mainly done for user convenience purposes.
*
* While a {@link DataSourceId} represents a unique and canonical identifier for an X-Pipe data source,
* there also exist easier and shorter ways to address a data source.
* This convenience comes at the price of ambiguity and instability for other types of references.
*/
public interface DataSourceReference { public interface DataSourceReference {
static DataSourceReference empty() { /**
return new Empty(); * Creates a reference that always refers to the latest data source.
*
* @see Latest
*/
static DataSourceReference latest() {
return new Latest();
} }
public static DataSourceReference parse(String s) { /**
* Creates a reference using only the data source name.
*
* @see Name
*/
static DataSourceReference name(String name) {
return new Name(name);
}
/**
* Convenience method for {@link #id(DataSourceId)}
*
* @see DataSourceId#fromString(String)
*/
static DataSourceReference id(String id) {
return new Id(DataSourceId.fromString(id));
}
/**
* Creates a reference by using a canonical data source id.
*
* @see Id
*/
static DataSourceReference id(DataSourceId id) {
return new Id(id);
}
static DataSourceReference parse(String s) {
if (s == null || s.trim().length() == 0) { if (s == null || s.trim().length() == 0) {
return new Empty(); return new Latest();
} }
if (s.contains(":")) { if (s.contains(":")) {
@ -27,15 +68,23 @@ public interface DataSourceReference {
enum Type { enum Type {
ID, ID,
NAME, NAME,
EMPTY LATEST
} }
Type getType(); Type getType();
DataSourceId getId(); DataSourceId getId();
String getName(); String getName();
/**
* Returns the internal string representation of this reference.
*/
String toRefString(); String toRefString();
String toString(); String toString();
/**
* A wrapper class for {@link DataSourceId} instances.
*/
@Value @Value
@AllArgsConstructor @AllArgsConstructor
static class Id implements DataSourceReference { static class Id implements DataSourceReference {
@ -81,6 +130,11 @@ public interface DataSourceReference {
} }
} }
/**
* Using only the data source name allows for a shorthand way of referring to data sources.
* This works as long there are no two different data sources with the same name in different collections.
* If this name reference is ambiguous, the data source referral fails.
*/
@Value @Value
@AllArgsConstructor @AllArgsConstructor
static class Name implements DataSourceReference { static class Name implements DataSourceReference {
@ -126,7 +180,12 @@ public interface DataSourceReference {
} }
} }
static class Empty implements DataSourceReference { /**
* Specifying the latest reference allows the user to always address the latest data source.
* Data source referral this way is unstable however as adding or
* removing data sources might change the referral behaviour and is therefore not recommended.
*/
static class Latest implements DataSourceReference {
@Override @Override
public String toRefString() { public String toRefString() {
@ -135,7 +194,7 @@ public interface DataSourceReference {
@Override @Override
public String toString() { public String toString() {
return "none"; return "latest";
} }
@Override @Override
@ -151,7 +210,7 @@ public interface DataSourceReference {
@Override @Override
public Type getType() { public Type getType() {
return Type.EMPTY; return Type.LATEST;
} }
@Override @Override

View file

@ -0,0 +1,16 @@
package io.xpipe.core.source;
import io.xpipe.core.store.DataStore;
public class TextDataSourceDescriptor<DS extends DataStore> implements DataSourceDescriptor<DS> {
@Override
public DataSourceInfo determineInfo(DS store) throws Exception {
return null;
}
@Override
public DataSourceType getType() {
return DataSourceType.TEXT;
}
}

View file

@ -0,0 +1,8 @@
package io.xpipe.core.source;
import java.nio.charset.Charset;
public interface TextReadConnection extends DataSourceConnection {
Charset getEncoding();
}

View file

@ -0,0 +1,8 @@
package io.xpipe.core.source;
import java.io.OutputStream;
public interface TextWriteConnection extends DataSourceConnection {
OutputStream getOutputStream();
}

View file

@ -11,8 +11,8 @@ public class DataSourceReferenceTest {
@Test @Test
public void parseValidParameters() { public void parseValidParameters() {
Assertions.assertEquals(DataSourceReference.parse(" ").getType(), DataSourceReference.Type.EMPTY); Assertions.assertEquals(DataSourceReference.parse(" ").getType(), DataSourceReference.Type.LATEST);
Assertions.assertEquals(DataSourceReference.parse(null).getType(), DataSourceReference.Type.EMPTY); Assertions.assertEquals(DataSourceReference.parse(null).getType(), DataSourceReference.Type.LATEST);
Assertions.assertEquals(DataSourceReference.parse("abc").getType(), DataSourceReference.Type.NAME); Assertions.assertEquals(DataSourceReference.parse("abc").getType(), DataSourceReference.Type.NAME);
Assertions.assertEquals(DataSourceReference.parse(" abc_ d e").getName(), "abc_ d e"); Assertions.assertEquals(DataSourceReference.parse(" abc_ d e").getName(), "abc_ d e");

View file

@ -8,6 +8,7 @@ plugins {
apply from: "$rootDir/deps/java.gradle" apply from: "$rootDir/deps/java.gradle"
apply from: "$rootDir/deps/javafx.gradle" apply from: "$rootDir/deps/javafx.gradle"
apply from: "$rootDir/deps/richtextfx.gradle" apply from: "$rootDir/deps/richtextfx.gradle"
apply from: "$rootDir/deps/preferencesfx.gradle"
apply from: "$rootDir/deps/jackson.gradle" apply from: "$rootDir/deps/jackson.gradle"
apply from: "$rootDir/deps/commons.gradle" apply from: "$rootDir/deps/commons.gradle"
apply from: "$rootDir/deps/lombok.gradle" apply from: "$rootDir/deps/lombok.gradle"
@ -26,4 +27,5 @@ repositories {
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation project(':fxcomps') implementation project(':fxcomps')
implementation group: 'com.dlsc.preferencesfx', name: 'preferencesfx-core', version: '11.8.0'
} }

View file

@ -1,6 +1,5 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.source.DataSourceId;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
@ -13,13 +12,7 @@ public interface SupportedApplicationProvider {
APPLICATION APPLICATION
} }
Region createTableRetrieveInstructions(ObservableValue<DataSourceId> id); Region createRetrieveInstructions(DataSourceProvider provider, ObservableValue<String> id);
Region createStructureRetrieveInstructions(ObservableValue<DataSourceId> id);
Region createTextRetrieveInstructions(ObservableValue<DataSourceId> id);
Region createRawRetrieveInstructions(ObservableValue<DataSourceId> id);
String getId(); String getId();

View file

@ -0,0 +1,49 @@
package io.xpipe.extension;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.StringExpression;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
public interface Translatable {
static <T extends Translatable> StringConverter<T> stringConverter() {
return new StringConverter<>() {
@Override public String toString(T t) {
return t == null ? null : t.toTranslatedString();
}
@Override public T fromString(String string) {
throw new AssertionError();
}
};
}
static <T extends Translatable> StringExpression asTranslatedString(ObservableValue<T> observableValue) {
return new StringBinding() {
{
super.bind(observableValue);
}
@Override
public void dispose() {
super.unbind(observableValue);
}
@Override
protected String computeValue() {
final T value = observableValue.getValue();
return (value == null) ? "null" : value.toTranslatedString();
}
@Override
public ObservableList<ObservableValue<?>> getDependencies() {
return FXCollections.singletonObservableList(observableValue);
}
};
}
String toTranslatedString();
}

View file

@ -8,6 +8,10 @@ import java.util.stream.Collectors;
public record CodeSnippet(List<CodeSnippet.Line> lines) { public record CodeSnippet(List<CodeSnippet.Line> lines) {
public String toString() {
return getRawString();
}
public String getRawString() { public String getRawString() {
return lines.stream().map(line -> line.elements().stream() return lines.stream().map(line -> line.elements().stream()
.map(Element::text).collect(Collectors.joining())) .map(Element::text).collect(Collectors.joining()))

View file

@ -3,6 +3,7 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; 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.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -23,8 +24,13 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
private final ObservableValue<Boolean> showLineNumbers; private final ObservableValue<Boolean> showLineNumbers;
private final ObservableValue<CodeSnippet> value; private final ObservableValue<CodeSnippet> value;
public CodeSnippetComp(boolean showLineNumbers, ObservableValue<CodeSnippet> value) {
this.showLineNumbers = new SimpleBooleanProperty(showLineNumbers);
this.value = PlatformUtil.wrap(value);
}
public CodeSnippetComp(ObservableValue<Boolean> showLineNumbers, ObservableValue<CodeSnippet> value) { public CodeSnippetComp(ObservableValue<Boolean> showLineNumbers, ObservableValue<CodeSnippet> value) {
this.showLineNumbers = showLineNumbers; this.showLineNumbers = PlatformUtil.wrap(showLineNumbers);
this.value = PlatformUtil.wrap(value); this.value = PlatformUtil.wrap(value);
} }
@ -88,10 +94,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
lineNumbers.getStyleClass().add("line-numbers"); lineNumbers.getStyleClass().add("line-numbers");
fillArea(lineNumbers, s); fillArea(lineNumbers, s);
value.addListener((c,o,n) -> { value.addListener((c,o,n) -> {
PlatformUtil.runLaterIfNeeded(() -> { fillArea(lineNumbers, s);
fillArea(lineNumbers, s);
s.setMaxHeight(5);
});
}); });
var spacer = new Region(); var spacer = new Region();
@ -103,7 +106,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
content.getChildren().add(0, lineNumbers); content.getChildren().add(0, lineNumbers);
content.getChildren().add(1, spacer); content.getChildren().add(1, spacer);
} }
PlatformUtil.wrap(showLineNumbers).addListener((c,o,n) -> { showLineNumbers.addListener((c,o,n) -> {
if (n) { if (n) {
content.getChildren().add(0, lineNumbers); content.getChildren().add(0, lineNumbers);
content.getChildren().add(1, spacer); content.getChildren().add(1, spacer);

View file

@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.Singular; import lombok.Singular;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.Map; import java.util.Map;
@Builder @Builder
@ -18,6 +19,16 @@ public class TrackEvent {
return this; return this;
} }
public TrackEventBuilder copy() {
var copy = builder();
copy.category = category;
copy.message = message;
copy.tags$key = new ArrayList<>(tags$key);
copy.tags$value = new ArrayList<>(tags$value);
copy.type = type;
return copy;
}
public void handle() { public void handle() {
build().handle(); build().handle();
} }
@ -35,6 +46,10 @@ public class TrackEvent {
return builder().type("info").message(message); return builder().type("info").message(message);
} }
public static TrackEventBuilder withWarn(String message) {
return builder().type("warn").message(message);
}
public static TrackEventBuilder withTrace(String message) { public static TrackEventBuilder withTrace(String message) {
return builder().type("trace").message(message); return builder().type("trace").message(message);
} }
@ -55,6 +70,10 @@ public class TrackEvent {
return builder().type("debug").message(message); return builder().type("debug").message(message);
} }
public static void debug(String cat, String message) {
builder().category(cat).type("debug").message(message).build().handle();
}
public static void debug(String message) { public static void debug(String message) {
builder().type("debug").message(message).build().handle(); builder().type("debug").message(message).build().handle();
} }
@ -63,6 +82,10 @@ public class TrackEvent {
builder().type("trace").message(message).build().handle(); builder().type("trace").message(message).build().handle();
} }
public static void info(String cat, String message) {
builder().category(cat).type("info").message(message).build().handle();
}
public static void trace(String cat, String message) { public static void trace(String cat, String message) {
builder().category(cat).type("trace").message(message).build().handle(); builder().category(cat).type("trace").message(message).build().handle();
} }

View file

@ -0,0 +1,14 @@
package io.xpipe.extension.prefs;
import io.xpipe.extension.I18n;
import io.xpipe.extension.Translatable;
public interface PrefsChoiceValue extends Translatable {
@Override
default String toTranslatedString() {
return I18n.get(getId());
}
String getId();
}

View file

@ -0,0 +1,51 @@
package io.xpipe.extension.prefs;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.List;
public class PrefsChoiceValueModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
addSerializer(PrefsChoiceValue.class, new PrefsChoiceValueSerializer());
addDeserializer(PrefsChoiceValue.class, new PrefsChoiceValueDeserializer());
context.addSerializers(_serializers);
context.addDeserializers(_deserializers);
}
public static class PrefsChoiceValueSerializer extends JsonSerializer<PrefsChoiceValue> {
@Override
public void serialize(PrefsChoiceValue value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(value.getId());
}
}
public static class PrefsChoiceValueDeserializer extends JsonDeserializer<PrefsChoiceValue> {
@Override
public PrefsChoiceValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
var id = p.getValueAsString();
Class<? extends PrefsChoiceValue> clazz = (Class<? extends PrefsChoiceValue>) ctxt.getContextualType().getRawClass();
try {
var list = (List<? extends PrefsChoiceValue>) clazz.getDeclaredField("SUPPORTED").get(null);
return list.stream().filter(v -> v.getId().equals(id)).findAny().orElse(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
}
}

View file

@ -0,0 +1,10 @@
package io.xpipe.extension.prefs;
import com.dlsc.preferencesfx.model.Setting;
import java.util.List;
public interface PrefsHandler {
void addSetting(List<String> category, String group, Setting<?,?> setting);
}

View file

@ -0,0 +1,6 @@
package io.xpipe.extension.prefs;
public interface PrefsProvider {
void addPrefs(PrefsHandler handler);
}

View file

@ -0,0 +1,21 @@
package io.xpipe.extension.prefs;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
public class PrefsProviders {
private static Set<PrefsProvider> ALL;
public static void init(ModuleLayer layer) {
if (ALL == null) {
ALL = ServiceLoader.load(layer, PrefsProvider.class).stream()
.map(ServiceLoader.Provider::get).collect(Collectors.toSet());
}
}
public static Set<PrefsProvider> getAll() {
return ALL;
}
}

View file

@ -1,5 +1,7 @@
import com.fasterxml.jackson.databind.Module;
import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.DataSourceProvider;
import io.xpipe.extension.SupportedApplicationProvider; import io.xpipe.extension.SupportedApplicationProvider;
import io.xpipe.extension.prefs.PrefsChoiceValueModule;
module io.xpipe.extension { module io.xpipe.extension {
requires io.xpipe.core; requires io.xpipe.core;
@ -13,12 +15,18 @@ module io.xpipe.extension {
exports io.xpipe.extension; exports io.xpipe.extension;
exports io.xpipe.extension.comp; exports io.xpipe.extension.comp;
exports io.xpipe.extension.event; exports io.xpipe.extension.event;
exports io.xpipe.extension.prefs;
uses DataSourceProvider; uses DataSourceProvider;
uses SupportedApplicationProvider; uses SupportedApplicationProvider;
uses io.xpipe.extension.I18n; uses io.xpipe.extension.I18n;
uses io.xpipe.extension.event.EventHandler; uses io.xpipe.extension.event.EventHandler;
uses io.xpipe.extension.prefs.PrefsProvider;
provides Module with PrefsChoiceValueModule;
requires com.dlsc.preferencesfx;
requires com.dlsc.formsfx;
requires java.desktop; requires java.desktop;
requires org.fxmisc.richtext; requires org.fxmisc.richtext;
requires org.fxmisc.flowless; requires org.fxmisc.flowless;
@ -26,4 +34,5 @@ module io.xpipe.extension {
requires org.fxmisc.wellbehavedfx; requires org.fxmisc.wellbehavedfx;
requires org.reactfx; requires org.reactfx;
requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.javafx;
requires com.fasterxml.jackson.databind;
} }