diff --git a/README.md b/README.md index f103d60..f6d72c4 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,22 @@ Pre-1.0 and pre-release. Nothing is published yet. The engine, the C ABI, and th If a bug reproduces through the `zu` CLI, it belongs in [tamnd/zu](https://github.com/tamnd/zu/issues), not here. +Inside this repository: + +| What | Where | +|---|---| +| The API, and nothing that touches the library | `zudb` | +| The Panama provider | `zudb-ffm` | +| The JNI provider, and the C shim it calls through | `zudb-jni` | +| The cases every provider owes, run by both of them | `zudb-tck` | +| Arrow, over the C Data Interface | `zudb-arrow` | +| JMH benchmarks | `zudb-bench` | +| The staged libraries, built by the release rather than by a clone | `zudb-native` | +| Every published name and the shape it is published in | `api/surface.txt` | +| Building the shim, staging the libraries | `scripts` | + +`api/surface.txt` is generated, one line per exported type and per member a caller outside the module can name, in the spirit of the `api/go1.N.txt` files Go holds itself to. `SurfaceTest` regenerates it and compares, so a change to the API is a change to that file in the same commit, where it is the first thing in the diff rather than something a user finds after the release. Write it down with `mvn -pl zudb test -Dzu.surface.write=true` and review it like any other file: a name that arrived is a minor release, a name that went or changed shape is a major one or a mistake, and the gate says which of the three a diff is while it is still a diff. It reads the compiled classes of the API module and links nothing, so it answers on a clone with no library staged and no Rust installed. + ## License Apache-2.0, same as the engine. diff --git a/api/surface.txt b/api/surface.txt new file mode 100644 index 0000000..5033c13 --- /dev/null +++ b/api/surface.txt @@ -0,0 +1,512 @@ +ctor public dev.zudb.Config(long, long, boolean) +ctor public dev.zudb.Diagnostic(dev.zudb.Status, java.lang.String, java.lang.String, java.lang.String, dev.zudb.Severity, int, int, int, java.lang.String, java.lang.String, boolean) +ctor public dev.zudb.Value$BindingTable() +ctor public dev.zudb.Value$Bool(boolean) +ctor public dev.zudb.Value$Field(java.lang.String, dev.zudb.Value) +ctor public dev.zudb.Value$Float(double) +ctor public dev.zudb.Value$Graph() +ctor public dev.zudb.Value$Int(long) +ctor public dev.zudb.Value$List(java.util.List) +ctor public dev.zudb.Value$Node(int, long) +ctor public dev.zudb.Value$Null() +ctor public dev.zudb.Value$Path(java.util.List) +ctor public dev.zudb.Value$Record(java.util.List) +ctor public dev.zudb.Value$Rel(int, long, long) +ctor public dev.zudb.Value$Str(java.lang.String) +ctor public dev.zudb.Value$Temporal(dev.zudb.Value$Temporal$Kind, long, int) +ctor public dev.zudb.ZuException$Position(int, int, int) +ctor public dev.zudb.spi.ProviderUnavailableException(java.lang.String) +ctor public dev.zudb.spi.ProviderUnavailableException(java.lang.String, java.lang.Throwable) +field public static final dev.zudb.Severity dev.zudb.Severity.EXCEPTION +field public static final dev.zudb.Severity dev.zudb.Severity.INFORMATIONAL +field public static final dev.zudb.Severity dev.zudb.Severity.NO_DATA +field public static final dev.zudb.Severity dev.zudb.Severity.SUCCESS +field public static final dev.zudb.Severity dev.zudb.Severity.WARNING +field public static final dev.zudb.Status dev.zudb.Status.CONFLICT +field public static final dev.zudb.Status dev.zudb.Status.CORRUPT +field public static final dev.zudb.Status dev.zudb.Status.DONE +field public static final dev.zudb.Status dev.zudb.Status.ERROR +field public static final dev.zudb.Status dev.zudb.Status.INTERRUPTED +field public static final dev.zudb.Status dev.zudb.Status.IO +field public static final dev.zudb.Status dev.zudb.Status.MISUSE +field public static final dev.zudb.Status dev.zudb.Status.MISUSE_CLOSED +field public static final dev.zudb.Status dev.zudb.Status.MISUSE_CONCURRENT +field public static final dev.zudb.Status dev.zudb.Status.OK +field public static final dev.zudb.Status dev.zudb.Status.UNKNOWN +field public static final dev.zudb.Status dev.zudb.Status.UNSUPPORTED +field public static final dev.zudb.Type dev.zudb.Type.BINDING_TABLE +field public static final dev.zudb.Type dev.zudb.Type.BOOL +field public static final dev.zudb.Type dev.zudb.Type.FLOAT +field public static final dev.zudb.Type dev.zudb.Type.GRAPH +field public static final dev.zudb.Type dev.zudb.Type.INT +field public static final dev.zudb.Type dev.zudb.Type.LIST +field public static final dev.zudb.Type dev.zudb.Type.NODE +field public static final dev.zudb.Type dev.zudb.Type.NULL +field public static final dev.zudb.Type dev.zudb.Type.PATH +field public static final dev.zudb.Type dev.zudb.Type.RECORD +field public static final dev.zudb.Type dev.zudb.Type.REL +field public static final dev.zudb.Type dev.zudb.Type.STR +field public static final dev.zudb.Type dev.zudb.Type.TEMPORAL +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.DATE +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.DURATION_DAY_TIME +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.DURATION_YEAR_MONTH +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.LOCAL_DATETIME +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.LOCAL_TIME +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.ZONED_DATETIME +field public static final dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.ZONED_TIME +field public static final java.lang.String dev.zudb.Zu.ABI_VERSION +field public static final long dev.zudb.Result.DEFAULT_BATCH +method public T dev.zudb.Connection.transaction(java.util.function.Supplier) +method public abstract boolean dev.zudb.Progress.at(long, long) +method public abstract boolean dev.zudb.spi.ZuBinding.connInTransaction(long) +method public abstract boolean dev.zudb.spi.ZuBinding.connUnregister(long, java.lang.String) +method public abstract boolean dev.zudb.spi.ZuBinding.databaseIsMemory(long) +method public abstract boolean dev.zudb.spi.ZuBinding.valueBoolean(long) +method public abstract dev.zudb.Diagnostic dev.zudb.spi.ZuBinding.resultNotice(long, int) +method public abstract dev.zudb.spi.ZuBinding dev.zudb.spi.ZuProvider.load(java.nio.file.Path) +method public abstract double dev.zudb.spi.ZuBinding.valueDouble(long) +method public abstract int dev.zudb.spi.ZuBinding.appenderColumns(long) +method public abstract int dev.zudb.spi.ZuBinding.resultCellType(long, long, int) +method public abstract int dev.zudb.spi.ZuBinding.resultCols(long) +method public abstract int dev.zudb.spi.ZuBinding.resultNotices(long) +method public abstract int dev.zudb.spi.ZuBinding.valueType(long) +method public abstract int dev.zudb.spi.ZuProvider.priority() +method public abstract java.lang.String dev.zudb.spi.ZuBinding.appenderColumnName(long, int) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.connRegisteredName(long, long) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.databasePath(long) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.resultCellString(long, long, int) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.resultColName(long, int) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.resultGqlstatus(long) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.valueField(long, long) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.valueString(long) +method public abstract java.lang.String dev.zudb.spi.ZuBinding.version() +method public abstract java.lang.String dev.zudb.spi.ZuProvider.name() +method public abstract java.nio.ByteBuffer dev.zudb.spi.ZuBinding.chunkValid(long, long, int, long) +method public abstract java.nio.ByteBuffer dev.zudb.spi.ZuBinding.colValid(long, int, long) +method public abstract java.nio.DoubleBuffer dev.zudb.spi.ZuBinding.chunkDoubles(long, long, int, long) +method public abstract java.nio.DoubleBuffer dev.zudb.spi.ZuBinding.colDoubles(long, int, long) +method public abstract java.nio.LongBuffer dev.zudb.spi.ZuBinding.chunkLongs(long, long, int, long) +method public abstract java.nio.LongBuffer dev.zudb.spi.ZuBinding.chunkNodeOffsets(long, long, int, long) +method public abstract java.nio.LongBuffer dev.zudb.spi.ZuBinding.colLongs(long, int, long) +method public abstract java.nio.LongBuffer dev.zudb.spi.ZuBinding.colNodeOffsets(long, int, long) +method public abstract long dev.zudb.spi.ZuBinding.appenderBuffered(long) +method public abstract long dev.zudb.spi.ZuBinding.appenderClose(long) +method public abstract long dev.zudb.spi.ZuBinding.appenderCommitted(long) +method public abstract long dev.zudb.spi.ZuBinding.appenderDiscard(long) +method public abstract long dev.zudb.spi.ZuBinding.appenderOpen(long, java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.chunkCount(long) +method public abstract long dev.zudb.spi.ZuBinding.connDuplicate(long) +method public abstract long dev.zudb.spi.ZuBinding.connRegisteredCount(long) +method public abstract long dev.zudb.spi.ZuBinding.connRowsRead(long) +method public abstract long dev.zudb.spi.ZuBinding.connect(long) +method public abstract long dev.zudb.spi.ZuBinding.create(java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.databaseCreate(java.lang.String, long, long, boolean) +method public abstract long dev.zudb.spi.ZuBinding.databaseMemory(long, long, boolean) +method public abstract long dev.zudb.spi.ZuBinding.databaseOpen(java.lang.String, long, long, boolean) +method public abstract long dev.zudb.spi.ZuBinding.execute(long) +method public abstract long dev.zudb.spi.ZuBinding.frameNew(java.lang.String, long, java.lang.Runnable) +method public abstract long dev.zudb.spi.ZuBinding.loaderCreate(java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.memory() +method public abstract long dev.zudb.spi.ZuBinding.open(java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.prepare(long, java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.query(long, java.lang.String) +method public abstract long dev.zudb.spi.ZuBinding.resultCell(long, long, int) +method public abstract long dev.zudb.spi.ZuBinding.resultRows(long) +method public abstract long dev.zudb.spi.ZuBinding.valueAt(long, long) +method public abstract long dev.zudb.spi.ZuBinding.valueLength(long) +method public abstract long dev.zudb.spi.ZuBinding.valueLong(long) +method public abstract long[] dev.zudb.spi.ZuBinding.chunk(long, long) +method public abstract long[] dev.zudb.spi.ZuBinding.configSet(long, long, boolean, java.lang.String, java.lang.String) +method public abstract long[] dev.zudb.spi.ZuBinding.valueNode(long) +method public abstract long[] dev.zudb.spi.ZuBinding.valueRel(long) +method public abstract long[] dev.zudb.spi.ZuBinding.valueTemporal(long) +method public abstract void dev.zudb.spi.ZuBinding.appendBoolean(long, boolean) +method public abstract void dev.zudb.spi.ZuBinding.appendBytes(long, java.nio.ByteBuffer) +method public abstract void dev.zudb.spi.ZuBinding.appendDouble(long, double) +method public abstract void dev.zudb.spi.ZuBinding.appendEndRow(long) +method public abstract void dev.zudb.spi.ZuBinding.appendLong(long, long) +method public abstract void dev.zudb.spi.ZuBinding.appendString(long, java.lang.String) +method public abstract void dev.zudb.spi.ZuBinding.appendTemporal(long, int, long) +method public abstract void dev.zudb.spi.ZuBinding.appenderFlush(long) +method public abstract void dev.zudb.spi.ZuBinding.appenderFree(long) +method public abstract void dev.zudb.spi.ZuBinding.begin(long, boolean) +method public abstract void dev.zudb.spi.ZuBinding.bindBoolean(long, java.lang.String, boolean) +method public abstract void dev.zudb.spi.ZuBinding.bindDouble(long, java.lang.String, double) +method public abstract void dev.zudb.spi.ZuBinding.bindLong(long, java.lang.String, long) +method public abstract void dev.zudb.spi.ZuBinding.bindNull(long, java.lang.String) +method public abstract void dev.zudb.spi.ZuBinding.bindString(long, java.lang.String, java.lang.String) +method public abstract void dev.zudb.spi.ZuBinding.bindTemporal(long, java.lang.String, int, long, int) +method public abstract void dev.zudb.spi.ZuBinding.commit(long) +method public abstract void dev.zudb.spi.ZuBinding.connClose(long) +method public abstract void dev.zudb.spi.ZuBinding.connInterrupt(long) +method public abstract void dev.zudb.spi.ZuBinding.connRegister(long, long) +method public abstract void dev.zudb.spi.ZuBinding.connSetProgress(long, dev.zudb.Progress, long) +method public abstract void dev.zudb.spi.ZuBinding.databaseClose(long) +method public abstract void dev.zudb.spi.ZuBinding.frameColumnBooleans(long, java.lang.String, java.nio.Buffer, long) +method public abstract void dev.zudb.spi.ZuBinding.frameColumnFloats(long, java.lang.String, java.nio.Buffer, long, int) +method public abstract void dev.zudb.spi.ZuBinding.frameColumnInts(long, java.lang.String, java.nio.Buffer, long, int, boolean, long, int) +method public abstract void dev.zudb.spi.ZuBinding.frameColumnStrings(long, java.lang.String, java.nio.Buffer, boolean, java.nio.Buffer, long) +method public abstract void dev.zudb.spi.ZuBinding.frameColumnViews(long, java.lang.String, java.nio.Buffer, java.util.List, long) +method public abstract void dev.zudb.spi.ZuBinding.frameFree(long) +method public abstract void dev.zudb.spi.ZuBinding.loaderColumnBooleans(long, java.lang.String, java.nio.IntBuffer) +method public abstract void dev.zudb.spi.ZuBinding.loaderColumnDoubles(long, java.lang.String, java.nio.DoubleBuffer) +method public abstract void dev.zudb.spi.ZuBinding.loaderColumnLongs(long, java.lang.String, java.nio.LongBuffer) +method public abstract void dev.zudb.spi.ZuBinding.loaderColumnStrings(long, java.lang.String, java.util.List) +method public abstract void dev.zudb.spi.ZuBinding.loaderColumnTemporal(long, java.lang.String, int, java.nio.LongBuffer) +method public abstract void dev.zudb.spi.ZuBinding.loaderEdges(long, java.nio.IntBuffer, java.nio.IntBuffer) +method public abstract void dev.zudb.spi.ZuBinding.loaderFinish(long) +method public abstract void dev.zudb.spi.ZuBinding.loaderFree(long) +method public abstract void dev.zudb.spi.ZuBinding.loaderTable(long, java.lang.String, java.lang.String, long) +method public abstract void dev.zudb.spi.ZuBinding.resultArrow(long, long, long, long) +method public abstract void dev.zudb.spi.ZuBinding.resultFree(long) +method public abstract void dev.zudb.spi.ZuBinding.rollback(long) +method public abstract void dev.zudb.spi.ZuBinding.stmtClose(long) +method public boolean dev.zudb.Appender.isFinished() +method public boolean dev.zudb.Config.readOnly() +method public boolean dev.zudb.Connection.inTransaction() +method public boolean dev.zudb.Connection.isClosed() +method public boolean dev.zudb.Connection.unregister(java.lang.String) +method public boolean dev.zudb.Database.isClosed() +method public boolean dev.zudb.Database.isMemory() +method public boolean dev.zudb.Diagnostic.retryable() +method public boolean dev.zudb.Frame.isClosed() +method public boolean dev.zudb.Loader.isFinished() +method public boolean dev.zudb.Result.isClosed() +method public boolean dev.zudb.Row.getBoolean(int) +method public boolean dev.zudb.Row.getBoolean(java.lang.String) +method public boolean dev.zudb.Row.isNull(int) +method public boolean dev.zudb.Row.isNull(java.lang.String) +method public boolean dev.zudb.Statement.isClosed() +method public boolean dev.zudb.Value$Bool.value() +method public boolean dev.zudb.ZuException.retryable() +method public dev.zudb.Appender dev.zudb.Appender.append(boolean) +method public dev.zudb.Appender dev.zudb.Appender.append(byte[]) +method public dev.zudb.Appender dev.zudb.Appender.append(dev.zudb.Value$Temporal$Kind, long) +method public dev.zudb.Appender dev.zudb.Appender.append(dev.zudb.Value$Temporal) +method public dev.zudb.Appender dev.zudb.Appender.append(double) +method public dev.zudb.Appender dev.zudb.Appender.append(java.lang.String) +method public dev.zudb.Appender dev.zudb.Appender.append(java.nio.ByteBuffer) +method public dev.zudb.Appender dev.zudb.Appender.append(java.time.Duration) +method public dev.zudb.Appender dev.zudb.Appender.append(java.time.LocalDate) +method public dev.zudb.Appender dev.zudb.Appender.append(java.time.LocalDateTime) +method public dev.zudb.Appender dev.zudb.Appender.append(java.time.LocalTime) +method public dev.zudb.Appender dev.zudb.Appender.append(java.time.Period) +method public dev.zudb.Appender dev.zudb.Appender.append(long) +method public dev.zudb.Appender dev.zudb.Appender.endRow() +method public dev.zudb.Appender dev.zudb.Appender.flush() +method public dev.zudb.Appender dev.zudb.Appender.row(java.lang.Object[]) +method public dev.zudb.Appender dev.zudb.Connection.appender(java.lang.String) +method public dev.zudb.Chunk dev.zudb.Result.chunk(long) +method public dev.zudb.Config dev.zudb.Config.with(java.lang.String, java.lang.String) +method public dev.zudb.Config dev.zudb.Config.withMemoryLimit(long) +method public dev.zudb.Config dev.zudb.Config.withReadOnly(boolean) +method public dev.zudb.Config dev.zudb.Config.withThreads(long) +method public dev.zudb.Connection dev.zudb.Connection.duplicate() +method public dev.zudb.Connection dev.zudb.Database.connect() +method public dev.zudb.Diagnostic dev.zudb.ZuException.diagnostic() +method public dev.zudb.Frame dev.zudb.Frame.booleans(java.lang.String, java.nio.ByteBuffer, long) +method public dev.zudb.Frame dev.zudb.Frame.column(java.lang.String, java.nio.DoubleBuffer) +method public dev.zudb.Frame dev.zudb.Frame.column(java.lang.String, java.nio.FloatBuffer) +method public dev.zudb.Frame dev.zudb.Frame.column(java.lang.String, java.nio.IntBuffer) +method public dev.zudb.Frame dev.zudb.Frame.column(java.lang.String, java.nio.LongBuffer) +method public dev.zudb.Frame dev.zudb.Frame.column(java.lang.String, java.nio.ShortBuffer) +method public dev.zudb.Frame dev.zudb.Frame.dates(java.lang.String, java.nio.IntBuffer) +method public dev.zudb.Frame dev.zudb.Frame.integers(java.lang.String, java.nio.Buffer, long, int, boolean, long, dev.zudb.Value$Temporal$Kind) +method public dev.zudb.Frame dev.zudb.Frame.strings(java.lang.String, java.nio.IntBuffer, java.nio.ByteBuffer) +method public dev.zudb.Frame dev.zudb.Frame.strings(java.lang.String, java.nio.LongBuffer, java.nio.ByteBuffer) +method public dev.zudb.Frame dev.zudb.Frame.timestamps(java.lang.String, java.nio.LongBuffer) +method public dev.zudb.Frame dev.zudb.Frame.views(java.lang.String, java.nio.ByteBuffer, java.util.List) +method public dev.zudb.Loader dev.zudb.Loader.booleanColumn(java.lang.String, java.nio.IntBuffer) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, boolean[]) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, double[]) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, java.lang.String[]) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, java.nio.DoubleBuffer) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, java.nio.LongBuffer) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, java.util.List) +method public dev.zudb.Loader dev.zudb.Loader.column(java.lang.String, long[]) +method public dev.zudb.Loader dev.zudb.Loader.edges(int[], int[]) +method public dev.zudb.Loader dev.zudb.Loader.edges(java.nio.IntBuffer, java.nio.IntBuffer) +method public dev.zudb.Loader dev.zudb.Loader.table(java.lang.String, java.lang.String, long) +method public dev.zudb.Loader dev.zudb.Loader.temporalColumn(java.lang.String, dev.zudb.Value$Temporal$Kind, java.nio.LongBuffer) +method public dev.zudb.Loader dev.zudb.Loader.temporalColumn(java.lang.String, dev.zudb.Value$Temporal$Kind, long[]) +method public dev.zudb.Result dev.zudb.Connection.query(java.lang.String) +method public dev.zudb.Result dev.zudb.Row.result() +method public dev.zudb.Result dev.zudb.Statement.execute() +method public dev.zudb.Row dev.zudb.Chunk.row(long) +method public dev.zudb.Row dev.zudb.Result.row(long) +method public dev.zudb.Severity dev.zudb.Diagnostic.severity() +method public dev.zudb.Severity dev.zudb.ZuException.severity() +method public dev.zudb.Statement dev.zudb.Connection.prepare(java.lang.String) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, boolean) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, dev.zudb.Value$Temporal$Kind, long, int) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, dev.zudb.Value$Temporal) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, double) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.lang.String) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.Duration) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.LocalDate) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.LocalDateTime) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.LocalTime) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.OffsetDateTime) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.OffsetTime) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, java.time.Period) +method public dev.zudb.Statement dev.zudb.Statement.bind(java.lang.String, long) +method public dev.zudb.Statement dev.zudb.Statement.bindNull(java.lang.String) +method public dev.zudb.Status dev.zudb.Diagnostic.status() +method public dev.zudb.Status dev.zudb.ZuException.status() +method public dev.zudb.Type dev.zudb.Result.cellType(long, int) +method public dev.zudb.Type dev.zudb.Row.type(int) +method public dev.zudb.Type dev.zudb.Row.type(java.lang.String) +method public dev.zudb.Value dev.zudb.Row.get(int) +method public dev.zudb.Value dev.zudb.Row.get(java.lang.String) +method public dev.zudb.Value dev.zudb.Value$Field.value() +method public dev.zudb.Value$Node dev.zudb.Row.getNode(int) +method public dev.zudb.Value$Node dev.zudb.Row.getNode(java.lang.String) +method public dev.zudb.Value$Temporal dev.zudb.Row.getTemporal(int) +method public dev.zudb.Value$Temporal dev.zudb.Row.getTemporal(java.lang.String) +method public dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal.kind() +method public dev.zudb.ZuException dev.zudb.Diagnostic.toException() +method public double dev.zudb.Row.getDouble(int) +method public double dev.zudb.Row.getDouble(java.lang.String) +method public double dev.zudb.Value$Float.value() +method public final boolean dev.zudb.Config.equals(java.lang.Object) +method public final boolean dev.zudb.Diagnostic.equals(java.lang.Object) +method public final boolean dev.zudb.Value$BindingTable.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Bool.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Field.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Float.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Graph.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Int.equals(java.lang.Object) +method public final boolean dev.zudb.Value$List.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Node.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Null.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Path.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Record.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Rel.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Str.equals(java.lang.Object) +method public final boolean dev.zudb.Value$Temporal.equals(java.lang.Object) +method public final boolean dev.zudb.ZuException$Position.equals(java.lang.Object) +method public final int dev.zudb.Config.hashCode() +method public final int dev.zudb.Diagnostic.hashCode() +method public final int dev.zudb.Value$BindingTable.hashCode() +method public final int dev.zudb.Value$Bool.hashCode() +method public final int dev.zudb.Value$Field.hashCode() +method public final int dev.zudb.Value$Float.hashCode() +method public final int dev.zudb.Value$Graph.hashCode() +method public final int dev.zudb.Value$Int.hashCode() +method public final int dev.zudb.Value$List.hashCode() +method public final int dev.zudb.Value$Node.hashCode() +method public final int dev.zudb.Value$Null.hashCode() +method public final int dev.zudb.Value$Path.hashCode() +method public final int dev.zudb.Value$Record.hashCode() +method public final int dev.zudb.Value$Rel.hashCode() +method public final int dev.zudb.Value$Str.hashCode() +method public final int dev.zudb.Value$Temporal.hashCode() +method public final int dev.zudb.ZuException$Position.hashCode() +method public final java.lang.String dev.zudb.Config.toString() +method public final java.lang.String dev.zudb.Diagnostic.toString() +method public final java.lang.String dev.zudb.Value$BindingTable.toString() +method public final java.lang.String dev.zudb.Value$Bool.toString() +method public final java.lang.String dev.zudb.Value$Field.toString() +method public final java.lang.String dev.zudb.Value$Float.toString() +method public final java.lang.String dev.zudb.Value$Graph.toString() +method public final java.lang.String dev.zudb.Value$Int.toString() +method public final java.lang.String dev.zudb.Value$List.toString() +method public final java.lang.String dev.zudb.Value$Node.toString() +method public final java.lang.String dev.zudb.Value$Null.toString() +method public final java.lang.String dev.zudb.Value$Path.toString() +method public final java.lang.String dev.zudb.Value$Record.toString() +method public final java.lang.String dev.zudb.Value$Rel.toString() +method public final java.lang.String dev.zudb.Value$Str.toString() +method public final java.lang.String dev.zudb.Value$Temporal.toString() +method public final java.lang.String dev.zudb.ZuException$Position.toString() +method public int dev.zudb.Appender.columns() +method public int dev.zudb.Diagnostic.column() +method public int dev.zudb.Diagnostic.line() +method public int dev.zudb.Diagnostic.offset() +method public int dev.zudb.Result.columnIndex(java.lang.String) +method public int dev.zudb.Result.columns() +method public int dev.zudb.Status.value() +method public int dev.zudb.Type.value() +method public int dev.zudb.Value$Node.table() +method public int dev.zudb.Value$Rel.table() +method public int dev.zudb.Value$Temporal$Kind.value() +method public int dev.zudb.Value$Temporal.offsetMinutes() +method public int dev.zudb.ZuException$Position.column() +method public int dev.zudb.ZuException$Position.line() +method public int dev.zudb.ZuException$Position.offset() +method public java.lang.String dev.zudb.Appender.columnName(int) +method public java.lang.String dev.zudb.Database.path() +method public java.lang.String dev.zudb.Diagnostic.code() +method public java.lang.String dev.zudb.Diagnostic.condition() +method public java.lang.String dev.zudb.Diagnostic.docUrl() +method public java.lang.String dev.zudb.Diagnostic.excerpt() +method public java.lang.String dev.zudb.Diagnostic.message() +method public java.lang.String dev.zudb.Frame.name() +method public java.lang.String dev.zudb.Result.columnName(int) +method public java.lang.String dev.zudb.Result.gqlstatus() +method public java.lang.String dev.zudb.Row.getString(int) +method public java.lang.String dev.zudb.Row.getString(java.lang.String) +method public java.lang.String dev.zudb.Row.toString() +method public java.lang.String dev.zudb.Value$Field.name() +method public java.lang.String dev.zudb.Value$Str.value() +method public java.nio.ByteBuffer dev.zudb.Chunk.valid(int) +method public java.nio.ByteBuffer dev.zudb.Result.valid(int) +method public java.nio.DoubleBuffer dev.zudb.Chunk.doubles(int) +method public java.nio.DoubleBuffer dev.zudb.Result.doubles(int) +method public java.nio.LongBuffer dev.zudb.Chunk.longs(int) +method public java.nio.LongBuffer dev.zudb.Chunk.nodeOffsets(int) +method public java.nio.LongBuffer dev.zudb.Result.longs(int) +method public java.nio.LongBuffer dev.zudb.Result.nodeOffsets(int) +method public java.time.Duration dev.zudb.Value$Temporal.toDuration() +method public java.time.LocalDate dev.zudb.Value$Temporal.toLocalDate() +method public java.time.LocalDateTime dev.zudb.Value$Temporal.toLocalDateTime() +method public java.time.LocalTime dev.zudb.Value$Temporal.toLocalTime() +method public java.time.OffsetDateTime dev.zudb.Value$Temporal.toOffsetDateTime() +method public java.time.OffsetTime dev.zudb.Value$Temporal.toOffsetTime() +method public java.time.Period dev.zudb.Value$Temporal.toPeriod() +method public java.time.ZoneOffset dev.zudb.Value$Temporal.offset() +method public java.util.Iterator dev.zudb.Result.iterator() +method public java.util.List dev.zudb.Result.notices() +method public java.util.List dev.zudb.Value$Record.fields() +method public java.util.List dev.zudb.Value$List.items() +method public java.util.List dev.zudb.Value$Path.items() +method public java.util.List dev.zudb.Connection.registeredNames() +method public java.util.List dev.zudb.Result.columnNames() +method public java.util.Optional dev.zudb.ZuException.position() +method public java.util.Optional dev.zudb.ZuException.caret() +method public java.util.Optional dev.zudb.ZuException.code() +method public java.util.Optional dev.zudb.ZuException.condition() +method public java.util.Optional dev.zudb.ZuException.docUrl() +method public java.util.Optional dev.zudb.ZuException.excerpt() +method public java.util.Spliterator dev.zudb.Result.spliterator() +method public java.util.stream.Stream dev.zudb.Result.chunks() +method public java.util.stream.Stream dev.zudb.Result.stream() +method public long dev.zudb.Appender.buffered() +method public long dev.zudb.Appender.committed() +method public long dev.zudb.Appender.discard() +method public long dev.zudb.Appender.finish() +method public long dev.zudb.Chunk.index() +method public long dev.zudb.Chunk.offset() +method public long dev.zudb.Chunk.rows() +method public long dev.zudb.Config.memoryLimit() +method public long dev.zudb.Config.threads() +method public long dev.zudb.Connection.registeredCount() +method public long dev.zudb.Connection.rowsRead() +method public long dev.zudb.Frame.rows() +method public long dev.zudb.Result.chunkCount() +method public long dev.zudb.Result.rows() +method public long dev.zudb.Row.getLong(int) +method public long dev.zudb.Row.getLong(java.lang.String) +method public long dev.zudb.Row.index() +method public long dev.zudb.Value$Int.value() +method public long dev.zudb.Value$Node.offset() +method public long dev.zudb.Value$Rel.source() +method public long dev.zudb.Value$Rel.target() +method public long dev.zudb.Value$Temporal.count() +method public static dev.zudb.Config dev.zudb.Config.defaults() +method public static dev.zudb.Config dev.zudb.Config.of(java.util.Map) +method public static dev.zudb.Connection dev.zudb.Connection.create(java.lang.String) +method public static dev.zudb.Connection dev.zudb.Connection.create(java.nio.file.Path) +method public static dev.zudb.Connection dev.zudb.Connection.memory() +method public static dev.zudb.Connection dev.zudb.Connection.open(java.lang.String) +method public static dev.zudb.Connection dev.zudb.Connection.open(java.nio.file.Path) +method public static dev.zudb.Database dev.zudb.Database.create(java.nio.file.Path) +method public static dev.zudb.Database dev.zudb.Database.create(java.nio.file.Path, dev.zudb.Config) +method public static dev.zudb.Database dev.zudb.Database.memory() +method public static dev.zudb.Database dev.zudb.Database.memory(dev.zudb.Config) +method public static dev.zudb.Database dev.zudb.Database.open(java.lang.String) +method public static dev.zudb.Database dev.zudb.Database.open(java.lang.String, dev.zudb.Config) +method public static dev.zudb.Database dev.zudb.Database.open(java.nio.file.Path) +method public static dev.zudb.Database dev.zudb.Database.open(java.nio.file.Path, dev.zudb.Config) +method public static dev.zudb.Diagnostic dev.zudb.Diagnostic.misuse(dev.zudb.Status, java.lang.String) +method public static dev.zudb.Diagnostic dev.zudb.Diagnostic.of(int, java.lang.String, java.lang.String, java.lang.String, int, int, int, int, java.lang.String, java.lang.String, boolean) +method public static dev.zudb.Frame dev.zudb.Frame.of(java.lang.String, long) +method public static dev.zudb.Frame dev.zudb.Frame.of(java.lang.String, long, java.lang.Runnable) +method public static dev.zudb.Loader dev.zudb.Loader.create(java.nio.file.Path) +method public static dev.zudb.Severity dev.zudb.Severity.valueOf(java.lang.String) +method public static dev.zudb.Severity[] dev.zudb.Severity.values() +method public static dev.zudb.Status dev.zudb.Status.of(int) +method public static dev.zudb.Status dev.zudb.Status.valueOf(java.lang.String) +method public static dev.zudb.Status[] dev.zudb.Status.values() +method public static dev.zudb.Type dev.zudb.Type.of(int) +method public static dev.zudb.Type dev.zudb.Type.valueOf(java.lang.String) +method public static dev.zudb.Type[] dev.zudb.Type.values() +method public static dev.zudb.Value$Null dev.zudb.Value$Null.instance() +method public static dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.of(int) +method public static dev.zudb.Value$Temporal$Kind dev.zudb.Value$Temporal$Kind.valueOf(java.lang.String) +method public static dev.zudb.Value$Temporal$Kind[] dev.zudb.Value$Temporal$Kind.values() +method public static java.lang.String dev.zudb.Zu.provider() +method public static java.lang.String dev.zudb.Zu.source() +method public static java.lang.String dev.zudb.Zu.version() +method public static java.lang.String dev.zudb.spi.Natives.flavour() +method public static java.lang.String dev.zudb.spi.Natives.platform() +method public static java.nio.file.Path dev.zudb.Zu.library() +method public static java.nio.file.Path dev.zudb.spi.Natives.unpack(java.lang.String, java.lang.String, java.lang.Class) +method public static java.util.Optional dev.zudb.Zu.availableProvider() +method public void dev.zudb.Appender.close() +method public void dev.zudb.Connection.begin() +method public void dev.zudb.Connection.beginReadOnly() +method public void dev.zudb.Connection.clearProgress() +method public void dev.zudb.Connection.close() +method public void dev.zudb.Connection.commit() +method public void dev.zudb.Connection.execute(java.lang.String) +method public void dev.zudb.Connection.interrupt() +method public void dev.zudb.Connection.onProgress(java.time.Duration, dev.zudb.Progress) +method public void dev.zudb.Connection.register(dev.zudb.Frame) +method public void dev.zudb.Connection.rollback() +method public void dev.zudb.Connection.transaction(java.lang.Runnable) +method public void dev.zudb.Database.close() +method public void dev.zudb.Frame.close() +method public void dev.zudb.Loader.close() +method public void dev.zudb.Loader.finish() +method public void dev.zudb.Result.close() +method public void dev.zudb.Result.exportArrow(long) +method public void dev.zudb.Result.exportArrow(long, long) +method public void dev.zudb.Statement.close() +type public abstract interface dev.zudb.Progress +type public abstract interface dev.zudb.Value sealed permits dev.zudb.Value$BindingTable, dev.zudb.Value$Bool, dev.zudb.Value$Float, dev.zudb.Value$Graph, dev.zudb.Value$Int, dev.zudb.Value$List, dev.zudb.Value$Node, dev.zudb.Value$Null, dev.zudb.Value$Path, dev.zudb.Value$Record, dev.zudb.Value$Rel, dev.zudb.Value$Str, dev.zudb.Value$Temporal +type public abstract interface dev.zudb.spi.ZuBinding +type public abstract interface dev.zudb.spi.ZuProvider +type public class dev.zudb.ZuClosedException extends dev.zudb.ZuProgrammingException +type public class dev.zudb.ZuConcurrentException extends dev.zudb.ZuProgrammingException +type public class dev.zudb.ZuConnectionException extends dev.zudb.ZuException +type public class dev.zudb.ZuDataException extends dev.zudb.ZuException +type public class dev.zudb.ZuException extends java.lang.RuntimeException +type public class dev.zudb.ZuInternalException extends dev.zudb.ZuException +type public class dev.zudb.ZuInterruptedException extends dev.zudb.ZuException +type public class dev.zudb.ZuProgrammingException extends dev.zudb.ZuException +type public class dev.zudb.ZuSyntaxException extends dev.zudb.ZuException +type public class dev.zudb.ZuTransactionException extends dev.zudb.ZuException +type public class dev.zudb.spi.ProviderUnavailableException extends java.lang.RuntimeException +type public final class dev.zudb.Appender implements java.lang.AutoCloseable +type public final class dev.zudb.Chunk +type public final class dev.zudb.Connection implements java.lang.AutoCloseable +type public final class dev.zudb.Database implements java.lang.AutoCloseable +type public final class dev.zudb.Frame implements java.lang.AutoCloseable +type public final class dev.zudb.Loader implements java.lang.AutoCloseable +type public final class dev.zudb.Result implements java.lang.AutoCloseable, java.lang.Iterable +type public final class dev.zudb.Row +type public final class dev.zudb.Statement implements java.lang.AutoCloseable +type public final class dev.zudb.Zu +type public final class dev.zudb.spi.Natives +type public final enum dev.zudb.Severity extends java.lang.Enum +type public final enum dev.zudb.Status extends java.lang.Enum +type public final enum dev.zudb.Type extends java.lang.Enum +type public final record dev.zudb.Config extends java.lang.Record +type public final record dev.zudb.Diagnostic extends java.lang.Record +type public static final enum dev.zudb.Value$Temporal$Kind extends java.lang.Enum +type public static final record dev.zudb.Value$BindingTable extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Bool extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Field extends java.lang.Record +type public static final record dev.zudb.Value$Float extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Graph extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Int extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$List extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Node extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Null extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Path extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Record extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Rel extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Str extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.Value$Temporal extends java.lang.Record implements dev.zudb.Value +type public static final record dev.zudb.ZuException$Position extends java.lang.Record diff --git a/zudb/src/test/java/dev/zudb/SurfaceTest.java b/zudb/src/test/java/dev/zudb/SurfaceTest.java new file mode 100644 index 0000000..3784c38 --- /dev/null +++ b/zudb/src/test/java/dev/zudb/SurfaceTest.java @@ -0,0 +1,324 @@ +package dev.zudb; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.TypeVariable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +/** + * The public surface, written down, so that moving it is something a reviewer + * sees. + * + *

A client's API is a promise, and the way that promise gets broken is + * never a decision anybody announced. It is a parameter that became an + * {@code Optional}, a class that stopped being public when it moved package, a + * method that gained an overload and made an existing call ambiguous. Each of + * those is one line in a diff that looks like an implementation change, and by + * the time a user finds out the release has gone. + * + *

So the surface lives in {@code api/surface.txt} at the root of the + * repository, one line per type and per member, and this test regenerates it + * and compares. A change to the API is then a change to that file, in the same + * commit, where it is the first thing in the diff rather than the last. That + * is the whole mechanism, and it is the same one zu-go uses, which matters + * because a reviewer moving between the clients should not have to learn a + * second one. + * + *

Run with {@code -Dzu.surface.write=true} to write the file instead of + * checking it. That is the one command to reach for when the change was + * meant, and running it is how you say so. + */ +class SurfaceTest { + + /** Where the surface is written down, from this module's directory. */ + private static final Path SURFACE = Paths.get("..", "api", "surface.txt"); + + /** The packages this module exports, which are the whole of the promise. */ + private static final List EXPORTED = List.of("dev.zudb", "dev.zudb.spi"); + + @Test + void theSurfaceIsWhatTheFileSaysItIs() throws IOException { + String found = surface(); + if (Boolean.getBoolean("zu.surface.write")) { + Files.createDirectories(SURFACE.getParent()); + Files.writeString(SURFACE, found, StandardCharsets.UTF_8); + return; + } + assertTrue( + Files.isRegularFile(SURFACE), + SURFACE.toAbsolutePath().normalize() + + " is missing: write it with mvn -pl zudb test -Dzu.surface.write=true"); + String written = Files.readString(SURFACE, StandardCharsets.UTF_8); + if (written.equals(found)) { + return; + } + // Not assertEquals on the whole thing. Five hundred lines printed twice + // is a report nobody reads, and the four lines that moved are the report. + TreeSet was = new TreeSet<>(written.lines().toList()); + TreeSet is = new TreeSet<>(found.lines().toList()); + StringBuilder moved = new StringBuilder(); + for (String line : is) { + if (!was.contains(line)) { + moved.append("\n + ").append(line); + } + } + for (String line : was) { + if (!is.contains(line)) { + moved.append("\n - ").append(line); + } + } + throw new AssertionError( + "the public surface moved and api/surface.txt did not:" + + moved + + "\n\nA name that arrived is a minor release. A name that went or changed shape is a" + + " major one, or a mistake. If it was meant, write the file down in the same commit" + + " with mvn -pl zudb test -Dzu.surface.write=true and say in the message which of" + + " those it is. If it was not, this is the review catching it."); + } + + @Test + void theSurfaceIsNotEmpty() throws IOException { + // A generator that quietly found nothing would make every future change + // pass, which is the one failure this test cannot report on itself. + long lines = surface().lines().count(); + assertTrue(lines > 300, "the surface came out at " + lines + " lines, which is not a client"); + } + + /** Every exported type and member, one a line, sorted. */ + private static String surface() throws IOException { + TreeSet lines = new TreeSet<>(); + for (Class type : types()) { + lines.add("type " + declaration(type)); + for (Field field : type.getDeclaredFields()) { + if (published(field.getModifiers()) && !field.isSynthetic()) { + lines.add( + "field " + + modifiers(field.getModifiers() & Modifier.fieldModifiers()) + + field.getGenericType().getTypeName() + + " " + + field.getDeclaringClass().getTypeName() + + "." + + field.getName()); + } + } + for (Constructor ctor : type.getDeclaredConstructors()) { + if (published(ctor.getModifiers()) && !ctor.isSynthetic()) { + lines.add( + "ctor " + + modifiers(ctor.getModifiers() & Modifier.constructorModifiers()) + + generics(ctor.getTypeParameters()) + + ctor.getDeclaringClass().getTypeName() + + arguments(ctor.getGenericParameterTypes()) + + thrown(ctor.getGenericExceptionTypes())); + } + } + for (Method method : type.getDeclaredMethods()) { + if (published(method.getModifiers()) && !method.isSynthetic() && !method.isBridge()) { + lines.add( + "method " + + modifiers(method.getModifiers() & Modifier.methodModifiers()) + + generics(method.getTypeParameters()) + + method.getGenericReturnType().getTypeName() + + " " + + method.getDeclaringClass().getTypeName() + + "." + + method.getName() + + arguments(method.getGenericParameterTypes()) + + thrown(method.getGenericExceptionTypes())); + } + } + } + StringBuilder out = new StringBuilder(); + for (String line : lines) { + out.append(line).append('\n'); + } + return out.toString(); + } + + /** The modifiers of a type that a caller outside the module can see. */ + private static final int MODIFIERS = + Modifier.PUBLIC | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.ABSTRACT; + + /** + * What a type is, spelled out here rather than taken from + * {@code toGenericString}. + * + *

That method is not the same on every JDK this client is built for. JDK + * 21 leaves the sealed marker out of it and JDK 25 puts it in, so a file + * written on one and checked on the other reports a change nobody made, and + * a gate that cries wolf on the version matrix is a gate people turn off. + * Everything here comes off the class file instead. + * + *

Sealing and the list of permitted subclasses are part of the promise, + * and so is the interface a type implements, which is how a caller knows a + * {@link Result} works in a for-each. + */ + private static String declaration(Class type) { + StringBuilder out = new StringBuilder(); + String modifiers = Modifier.toString(type.getModifiers() & MODIFIERS); + if (!modifiers.isEmpty()) { + out.append(modifiers).append(' '); + } + out.append(kind(type)).append(' ').append(type.getName()).append(parameters(type)); + if (type.isSealed()) { + TreeSet permitted = new TreeSet<>(); + for (Class each : type.getPermittedSubclasses()) { + permitted.add(each.getTypeName()); + } + out.append(" sealed permits ").append(String.join(", ", permitted)); + } + if (type.getSuperclass() != null && type.getSuperclass() != Object.class) { + out.append(" extends ").append(type.getGenericSuperclass().getTypeName()); + } + TreeSet faces = new TreeSet<>(); + for (java.lang.reflect.Type each : type.getGenericInterfaces()) { + faces.add(each.getTypeName()); + } + if (!faces.isEmpty()) { + out.append(type.isInterface() ? " extends " : " implements ") + .append(String.join(", ", faces)); + } + return out.toString(); + } + + /** Which of the five a type is. */ + private static String kind(Class type) { + if (type.isAnnotation()) { + return "@interface"; + } + if (type.isInterface()) { + return "interface"; + } + if (type.isEnum()) { + return "enum"; + } + if (type.isRecord()) { + return "record"; + } + return "class"; + } + + /** Modifiers with a space after them, or nothing at all. */ + private static String modifiers(int bits) { + String words = Modifier.toString(bits); + return words.isEmpty() ? "" : words + " "; + } + + /** The argument list of a constructor or a method, brackets and all. */ + private static String arguments(java.lang.reflect.Type[] args) { + List each = new ArrayList<>(); + for (java.lang.reflect.Type arg : args) { + each.add(arg.getTypeName()); + } + return "(" + String.join(", ", each) + ")"; + } + + /** What a member declares it throws, or nothing at all. */ + private static String thrown(java.lang.reflect.Type[] exceptions) { + if (exceptions.length == 0) { + return ""; + } + List each = new ArrayList<>(); + for (java.lang.reflect.Type exception : exceptions) { + each.add(exception.getTypeName()); + } + return " throws " + String.join(", ", each); + } + + /** The type parameters a member declares, with a space after them. */ + private static String generics(TypeVariable[] vars) { + String written = parameters(vars); + return written.isEmpty() ? "" : written + " "; + } + + /** The type parameters a type declares, or nothing at all. */ + private static String parameters(Class type) { + return parameters(type.getTypeParameters()); + } + + /** The type parameters a type or a member declares, or nothing at all. */ + private static String parameters(TypeVariable[] vars) { + if (vars.length == 0) { + return ""; + } + List each = new ArrayList<>(); + for (TypeVariable var : vars) { + StringBuilder one = new StringBuilder(var.getName()); + List bounds = new ArrayList<>(); + for (java.lang.reflect.Type bound : var.getBounds()) { + if (bound != Object.class) { + bounds.add(bound.getTypeName()); + } + } + if (!bounds.isEmpty()) { + one.append(" extends ").append(String.join(" & ", bounds)); + } + each.add(one.toString()); + } + return "<" + String.join(", ", each) + ">"; + } + + /** Whether a member is one a caller outside this module can name. */ + private static boolean published(int modifiers) { + return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); + } + + /** + * The exported types, read off the classes this module was compiled to. + * + *

Reading the directory rather than a list of names is the point: a type + * somebody adds turns up here without anybody remembering to add it, which + * is the failure mode of every hand-kept inventory. + */ + private static List> types() throws IOException { + Path classes = Paths.get("target", "classes"); + assertTrue( + Files.isDirectory(classes), + classes.toAbsolutePath().normalize() + " is missing: the build writes it before the tests"); + List> found = new ArrayList<>(); + try (Stream walk = Files.walk(classes)) { + for (Path each : walk.toList()) { + String name = classes.relativize(each).toString(); + if (!name.endsWith(".class")) { + continue; + } + name = + name.substring(0, name.length() - ".class".length()) + .replace(java.io.File.separatorChar, '.') + .replace('/', '.'); + if (name.equals("module-info") || name.endsWith("package-info")) { + continue; + } + String pkg = name.contains(".") ? name.substring(0, name.lastIndexOf('.')) : ""; + if (!EXPORTED.contains(pkg)) { + continue; + } + Class type; + try { + type = Class.forName(name, false, SurfaceTest.class.getClassLoader()); + } catch (ClassNotFoundException cannot) { + throw new IllegalStateException(name + " is on disk and not loadable", cannot); + } + if (published(type.getModifiers())) { + found.add(type); + } + } + } + assertTrue(found.size() > 20, "found " + found.size() + " exported types, which is too few"); + found.sort((a, b) -> a.getName().compareTo(b.getName())); + return found; + } +}