You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/10/08 11:04:51 UTC
(sis) branch geoapi-4.0 updated (c2a9ff0285 -> c372a46be1)
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
from c2a9ff0285 feat(Geometry): reorganize GeometryFactory to allow futur configuration parameters
new 86e52fe873 Add `Vector.doubleStream()` et `Vector.createNumberFormat(Locale)` methods.
new d8e23f1abe Add a `TableRowList` internal class and uses it in `PropertyFormat` for better formatting of tabular data.
new e88fd77241 Rename GeoTIFF `Localization` class as `ModelTiePoints` and use the class added in previous commit for tabular formatting.
new c372a46be1 Recognize tabular metadata property values in the JavaFX application as well.
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../apache/sis/storage/geotiff/NativeMetadata.java | 9 +-
.../geotiff/reader/GridGeometryBuilder.java | 6 +-
.../{Localization.java => ModelTiePoints.java} | 77 ++++++++++++--
...calizationTest.java => ModelTiePointsTest.java} | 14 +--
.../main/org/apache/sis/math/ArrayVector.java | 17 +++
.../main/org/apache/sis/math/DecimalFunctions.java | 2 +-
.../main/org/apache/sis/math/RepeatedVector.java | 12 +++
.../main/org/apache/sis/math/Vector.java | 48 ++++++++-
.../apache/sis/util/internal/shared/Numerics.java | 2 +-
.../sis/util/internal/shared/PropertyFormat.java | 66 +++++++++---
.../sis/util/internal/shared/TableRowList.java | 103 ++++++++++++++++++
.../test/org/apache/sis/math/VectorTest.java | 4 +
.../sis/gui/controls/FormatTableCellFactory.java | 117 +++++++++++++++++++++
.../sis/gui/coverage/ImagePropertyExplorer.java | 16 +--
.../org/apache/sis/gui/internal/PropertyView.java | 43 ++++++--
15 files changed, 484 insertions(+), 52 deletions(-)
rename endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/{Localization.java => ModelTiePoints.java} (83%)
rename endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/{LocalizationTest.java => ModelTiePointsTest.java} (95%)
create mode 100644 endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TableRowList.java
create mode 100644 optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java
(sis) 03/04: Rename GeoTIFF `Localization` class as `ModelTiePoints` and use the class added in previous commit for tabular formatting.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit e88fd77241f423f60c06b504a3b9a9c39e54acb9
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Oct 8 12:57:22 2026 +0200
Rename GeoTIFF `Localization` class as `ModelTiePoints` and use the class added in previous commit for tabular formatting.
---
.../apache/sis/storage/geotiff/NativeMetadata.java | 9 ++-
.../geotiff/reader/GridGeometryBuilder.java | 6 +-
.../{Localization.java => ModelTiePoints.java} | 77 +++++++++++++++++++---
...calizationTest.java => ModelTiePointsTest.java} | 14 ++--
4 files changed, 86 insertions(+), 20 deletions(-)
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/NativeMetadata.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/NativeMetadata.java
index 39c7a23780..6f7abffbfc 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/NativeMetadata.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/NativeMetadata.java
@@ -16,7 +16,6 @@
*/
package org.apache.sis.storage.geotiff;
-import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.LinkedHashMap;
@@ -41,6 +40,7 @@ import org.apache.sis.storage.geotiff.base.GeoKeys;
import org.apache.sis.storage.geotiff.base.Tags;
import org.apache.sis.storage.geotiff.reader.Type;
import org.apache.sis.storage.geotiff.reader.GeoKeysLoader;
+import org.apache.sis.storage.geotiff.reader.ModelTiePoints;
import org.apache.sis.storage.geotiff.reader.XMLMetadata;
@@ -116,7 +116,7 @@ final class NativeMetadata extends GeoKeysLoader {
input.mark();
try {
input.seek(isClassic ? 2*Short.BYTES : 4*Short.BYTES);
- final Set<Long> doneIFD = new HashSet<>();
+ final var doneIFD = new HashSet<Long>();
long nextIFD;
/*
* Following loop is a simplified copy of `Reader.getImageFileDirectory(int)` method,
@@ -187,6 +187,10 @@ final class NativeMetadata extends GeoKeysLoader {
}
break;
}
+ case (short) TAG_MODEL_TIE_POINT: {
+ value = new ModelTiePoints(type.readAsVector(input, count));
+ break;
+ }
default: {
value = type.readAsObject(input, count);
if (value instanceof Vector) {
@@ -278,6 +282,7 @@ final class NativeMetadata extends GeoKeysLoader {
}
return String.join(", ", items);
});
+ break;
}
}
}
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
index cf6abb4b17..7e779168f7 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
@@ -228,7 +228,7 @@ public final class GridGeometryBuilder extends GeoKeysLoader {
int nearest = 0; // Index of the record nearest to origin.
double distance = Double.POSITIVE_INFINITY; // Distance squared of the nearest record.
final int size = modelTiePoints.size();
- for (int i=0; i<size; i += Localization.RECORD_LENGTH) {
+ for (int i=0; i<size; i += ModelTiePoints.RECORD_LENGTH) {
double t;
final double d = (t = modelTiePoints.doubleValue(i )) * t
+ (t = modelTiePoints.doubleValue(i + 1)) * t
@@ -252,7 +252,7 @@ public final class GridGeometryBuilder extends GeoKeysLoader {
final int trCol = affine.getNumCol() - 1;
for (int j=0; j<numDim; j++) {
final double src = -modelTiePoints.doubleValue(nearest + j);
- final double tgt = modelTiePoints.doubleValue(nearest + j + Localization.RECORD_LENGTH / BIDIMENSIONAL);
+ final double tgt = modelTiePoints.doubleValue(nearest + j + ModelTiePoints.RECORD_LENGTH / BIDIMENSIONAL);
var t = DoubleDouble.of(src, decimal).multiply(affine.getNumber(j,j), decimal).add(tgt, decimal);
affine.setNumber(j, trCol, t);
}
@@ -362,7 +362,7 @@ public final class GridGeometryBuilder extends GeoKeysLoader {
gridToCRS = factory.createAffineTransform(m);
} else if (modelTiePoints != null) {
anchor = PixelInCell.CELL_CENTER;
- gridToCRS = Localization.nonLinear(modelTiePoints);
+ gridToCRS = new ModelTiePoints(modelTiePoints).nonLinear();
gridToCRS = factory.createPassThroughTransform(0, gridToCRS, spatialDimension - BIDIMENSIONAL);
if (temporalCRS != null) {
gridToCRS = MathTransforms.compound(gridToCRS, MathTransforms.linear(Double.NaN, timeCoordinate));
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Localization.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ModelTiePoints.java
similarity index 83%
rename from endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Localization.java
rename to endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ModelTiePoints.java
index 731c24b302..c65aaa000b 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Localization.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ModelTiePoints.java
@@ -21,6 +21,9 @@ import java.util.Set;
import java.util.Map;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Locale;
+import java.text.Format;
import org.opengis.geometry.Envelope;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.MathTransform;
@@ -29,20 +32,19 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
import org.apache.sis.referencing.factory.InternalFactoryException;
+import org.apache.sis.util.internal.shared.TableRowList;
import org.apache.sis.math.Vector;
/**
* The conversion or transformation from pixel coordinates to model coordinates.
+ * Used for analyzing the {@code TAG_MODEL_TIE_POINT} data from a GeoTIFF file.
* The target CRS may be the image CRS if the image is "georeferenceable" instead of georeferenced.
*
- * This code is provided in a separated class for making easier to move it to some shared location
- * if another data store needs similar functionality in the future.
- *
* @author Martin Desruisseaux (Geomatys)
* @author Jonatas Fischer
*/
-final class Localization {
+public final class ModelTiePoints extends TableRowList<Vector, Double> {
/**
* Number of floating point values in each (I,J,K,X,Y,Z) record.
*/
@@ -57,9 +59,68 @@ final class Localization {
private static final double PRECISION = 1E-6;
/**
- * Do not allow instantiation of this class.
+ * Coordinates of the model tie points read from GeoTIFF file.
+ */
+ private final Vector coordinates;
+
+ /**
+ * Creates a new localization grid.
+ *
+ * @param coordinates the model tie points read from GeoTIFF file.
+ */
+ public ModelTiePoints(final Vector coordinates) {
+ this.coordinates = coordinates;
+ }
+
+ /**
+ * Returns the number of rows in the model tie points.
+ *
+ * @return number of (I,J,K,X,Y,Z) records.
+ */
+ @Override
+ public int size() {
+ return coordinates.size() / RECORD_LENGTH;
+ }
+
+ /**
+ * Returns the column headers.
+ * The returned array length is {@link #RECORD_LENGTH}.
+ */
+ @Override
+ public String[] columns() {
+ return new String[] {"i", "j", "k", "x", "y", "z"};
+ }
+
+ /**
+ * Returns the value in the specified row and column.
+ */
+ @Override
+ public Double get(final int row, final int column) {
+ Objects.checkIndex(row, size());
+ Objects.checkIndex(column, RECORD_LENGTH);
+ return coordinates.doubleValue(row * RECORD_LENGTH + column);
+ }
+
+ /**
+ * Returns the source and (I,J,K,X,Y,Z) record in the given row.
+ */
+ @Override
+ public Vector get(int row) {
+ Objects.checkIndex(row, size());
+ row *= RECORD_LENGTH;
+ return coordinates.subList(row, row + RECORD_LENGTH);
+ }
+
+ /**
+ * Returns the format to use for the given column.
+ *
+ * @param locale the locale of the format to create.
+ * @param column the column for which to get a format.
*/
- private Localization() {
+ @Override
+ public Format createFormat(final Locale locale, final int column) {
+ return coordinates.subSampling(Objects.checkIndex(column, RECORD_LENGTH), RECORD_LENGTH, size())
+ .createNumberFormat(locale);
}
/**
@@ -69,8 +130,8 @@ final class Localization {
* @param modelTiePoints the tie points to use for computing {@code gridToCRS}.
* @return the grid geometry created from above properties. Never null.
*/
- static MathTransform nonLinear(final Vector modelTiePoints) throws FactoryException, TransformException {
- return localizationGrid(modelTiePoints, null);
+ final MathTransform nonLinear() throws FactoryException, TransformException {
+ return localizationGrid(coordinates, null);
}
/**
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/LocalizationTest.java b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/ModelTiePointsTest.java
similarity index 95%
rename from endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/LocalizationTest.java
rename to endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/ModelTiePointsTest.java
index 111ea6a9a9..f95781e93e 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/LocalizationTest.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/ModelTiePointsTest.java
@@ -38,7 +38,7 @@ import org.apache.sis.test.TestCase;
*
* @author Jonatas Fischer
*/
-public final class LocalizationTest extends TestCase {
+public final class ModelTiePointsTest extends TestCase {
/**
* Tolerance threshold, in degrees, when comparing tie point coordinates.
* This is about one metre, while the tie points of the grids tested here
@@ -55,7 +55,7 @@ public final class LocalizationTest extends TestCase {
/**
* Creates a new test case.
*/
- public LocalizationTest() {
+ public ModelTiePointsTest() {
}
/**
@@ -95,7 +95,7 @@ public final class LocalizationTest extends TestCase {
/**
* Returns the pixel coordinates of {@value #GRID_SIZE} tie points spaced by the given step,
* except the last point which is closer to its predecessor. This is the spacing of Sentinel 1
- * images, and the reason why {@link Localization} splits irregular grids in four parts.
+ * images, and the reason why {@link ModelTiePoints} splits irregular grids in four parts.
*
* @param step step between two consecutive tie points, except the last two.
* @param last step between the two last tie points.
@@ -121,7 +121,7 @@ public final class LocalizationTest extends TestCase {
* @return the (I,J,K,X,Y,Z) records of the tie points.
*/
private static Vector tiePoints(final double[] columns, final double[] rows) {
- final double[] records = new double[columns.length * rows.length * Localization.RECORD_LENGTH];
+ final double[] records = new double[columns.length * rows.length * ModelTiePoints.RECORD_LENGTH];
int p = 0;
for (final double y : rows) {
for (final double x : columns) {
@@ -147,11 +147,11 @@ public final class LocalizationTest extends TestCase {
*/
private static void verify(final double[] columns, final double[] rows) throws Exception {
final Vector tiePoints = tiePoints(columns, rows);
- final MathTransform gridToCRS = Localization.nonLinear(tiePoints);
+ final MathTransform gridToCRS = new ModelTiePoints(tiePoints).nonLinear();
assertNotNull(gridToCRS);
final double[] source = new double[2];
final double[] target = new double[2];
- for (int i=0; i<tiePoints.size(); i += Localization.RECORD_LENGTH) {
+ for (int i=0; i<tiePoints.size(); i += ModelTiePoints.RECORD_LENGTH) {
source[0] = tiePoints.doubleValue(i);
source[1] = tiePoints.doubleValue(i+1);
gridToCRS.transform(source, 0, target, 0, 1);
@@ -194,7 +194,7 @@ public final class LocalizationTest extends TestCase {
/**
* Tests a grid where the steps differ by one pixel on a single axis. Only two of the four parts
- * in which {@code Localization} would split such a grid receive points; the empty parts shall
+ * in which {@code ModelTiePoints} would split such a grid receive points; the empty parts shall
* not cause an {@link IndexOutOfBoundsException}.
*
* <p>This is the case of ICEYE Single Look Complex images of 114644 × 16714 pixels:
(sis) 01/04: Add `Vector.doubleStream()` et `Vector.createNumberFormat(Locale)` methods.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 86e52fe8734d3d9207d7aeb02edaa1b692bdbb26
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Oct 8 12:54:08 2026 +0200
Add `Vector.doubleStream()` et `Vector.createNumberFormat(Locale)` methods.
---
.../main/org/apache/sis/math/ArrayVector.java | 17 ++++++++
.../main/org/apache/sis/math/DecimalFunctions.java | 2 +-
.../main/org/apache/sis/math/RepeatedVector.java | 12 ++++++
.../main/org/apache/sis/math/Vector.java | 48 +++++++++++++++++++++-
.../test/org/apache/sis/math/VectorTest.java | 4 ++
5 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
index baea507adc..551bb722b0 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
@@ -26,6 +26,7 @@ import java.nio.FloatBuffer;
import java.nio.DoubleBuffer;
import java.util.Arrays;
import java.util.Optional;
+import java.util.stream.DoubleStream;
import java.util.function.IntSupplier;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Numbers;
@@ -44,6 +45,7 @@ import org.apache.sis.measure.NumberRange;
*
* @param <E> the base type of elements in the vector.
*/
+@SuppressWarnings("EqualsAndHashcode")
abstract class ArrayVector<E extends Number> extends Vector implements CheckedContainer<E>, Serializable {
/**
* For cross-version compatibility.
@@ -307,6 +309,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
return Optional.of(DoubleBuffer.wrap(array));
}
+ /** Returns a stream of current data. */
+ @Override public DoubleStream doubleStream() {
+ return Arrays.stream(array);
+ }
+
/** Returns a copy of current data as a floating point array. */
@Override public double[] doubleValues() {
return array.clone();
@@ -615,6 +622,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
return Optional.of(LongBuffer.wrap(array));
}
+ /** Returns a stream of current data. */
+ @Override public DoubleStream doubleStream() {
+ return Arrays.stream(array).mapToDouble((value) -> value);
+ }
+
/** Applies hash code contract specified {@link Vector#hashCode()}. */
@Override public final int hashCode() {
return Arrays.hashCode(array);
@@ -748,6 +760,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
return Optional.of(IntBuffer.wrap(array));
}
+ /** Returns a stream of current data. */
+ @Override public DoubleStream doubleStream() {
+ return Arrays.stream(array).mapToDouble((value) -> value);
+ }
+
/** Applies hash code contract specified {@link Vector#hashCode()}. */
@Override public final int hashCode() {
return Arrays.hashCode(array);
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
index 62bb3b7288..172dbda70e 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
@@ -523,7 +523,7 @@ public final class DecimalFunctions {
}
/**
- * Returns {@code true} if the given numbers or equal or differ only by {@code accurate}
+ * Returns {@code true} if the given numbers are equal or differ only by {@code accurate}
* having more non-zero trailing decimal fraction digits than {@code approximate}.
*
* <table class="sis">
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/RepeatedVector.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/RepeatedVector.java
index 21a3ce8dda..3b156133f9 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/RepeatedVector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/RepeatedVector.java
@@ -17,6 +17,8 @@
package org.apache.sis.math;
import java.io.Serializable;
+import java.text.NumberFormat;
+import java.util.Locale;
import java.util.Objects;
import java.util.function.IntSupplier;
import org.apache.sis.measure.NumberRange;
@@ -284,4 +286,14 @@ opti: if (count > 1 && cycleLength * occurrences >= size) {
}
return super.createSubSampling(first, step, length);
}
+
+ /**
+ * Suggests a format configured to the data contained in this vector.
+ * Because this vector is a repetition of the {@linkplain #base} vector,
+ * delegating to the base vector is sufficient.
+ */
+ @Override
+ public NumberFormat createNumberFormat(final Locale locale) {
+ return base.createNumberFormat(locale);
+ }
}
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
index 517d867515..9a850a09e5 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
@@ -18,12 +18,16 @@ package org.apache.sis.math;
import java.io.Serializable;
import java.nio.Buffer;
+import java.text.NumberFormat;
+import java.util.Locale;
import java.util.Arrays;
import java.util.AbstractList;
import java.util.RandomAccess;
import java.util.StringJoiner;
import java.util.Optional;
import java.util.Objects;
+import java.util.stream.IntStream;
+import java.util.stream.DoubleStream;
import java.util.function.IntSupplier;
import static java.util.logging.Logger.getLogger;
import org.apache.sis.measure.NumberRange;
@@ -242,7 +246,7 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces
* not {@link Float#TYPE}.
*
* <p>The information returned by this method is only indicative; it is not guaranteed to specify accurately
- * this kind of objects returned by the {@link #get(int)} method. There is various situations where the types
+ * the kind of objects returned by the {@link #get(int)} method. There is various situations where the types
* may not match:</p>
*
* <ul>
@@ -1503,6 +1507,19 @@ search: for (;;) {
return Optional.empty();
}
+ /**
+ * Returns all values in a stream of double precision floating point numbers.
+ *
+ * <p>The default implementation invokes {@link #doubleValue(int)} for all indices from 0 inclusive
+ * to {@link #size()} exclusive. Subclasses may override with more efficient implementation.</p>
+ *
+ * @return a stream of all floating point values in this vector.
+ * @since 1.7
+ */
+ public DoubleStream doubleStream() {
+ return IntStream.range(0, size()).mapToDouble(this::doubleValue);
+ }
+
/**
* Copies all values in an array of double precision floating point numbers.
* This method is for inter-operability with APIs requiring an array of primitive type.
@@ -1603,6 +1620,35 @@ search: for (;;) {
return ArrayVector.newInstance(array, isUnsigned());
}
+ /**
+ * Suggests a format configured with a number of fraction digits adapted to the data contained in this vector.
+ * The format will not necessarily show all significant digits. Instead, this method can use heuristic rules
+ * for choosing a number of fraction digits sufficient for distinguishing the majority of the values.
+ *
+ * @param locale the desired locale.
+ * @return format configured for the data contained in this vector.
+ *
+ * @since 1.7
+ */
+ public NumberFormat createNumberFormat(final Locale locale) {
+ if (isInteger()) {
+ return NumberFormat.getIntegerInstance(locale);
+ }
+ final int digits;
+ final Number increment = increment(0);
+ if (increment != null) {
+ digits = DecimalFunctions.fractionDigitsForDelta(increment.doubleValue(), false);
+ } else {
+ final var stats = new Statistics(null);
+ doubleStream().forEach(stats);
+ digits = Numerics.suggestFractionDigits(stats);
+ }
+ final NumberFormat format = NumberFormat.getNumberInstance(locale);
+ format.setMinimumFractionDigits(digits);
+ format.setMaximumFractionDigits(digits);
+ return format;
+ }
+
/**
* Returns a string representation of this vector.
*
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/math/VectorTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/math/VectorTest.java
index f5e69f908d..8dc4f00034 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/math/VectorTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/math/VectorTest.java
@@ -30,6 +30,7 @@ import org.apache.sis.test.TestCase;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class VectorTest extends TestCase {
/**
* The tested vector.
@@ -182,6 +183,7 @@ public final class VectorTest extends TestCase {
assertSame(vector, Vector.create(vector, false));
assertEquals(array.length, vector.size());
assertEquals(Float.class, vector.getElementType());
+ assertArrayEquals(array, vector.floatValues());
/*
* Tests element values.
*/
@@ -205,6 +207,8 @@ public final class VectorTest extends TestCase {
assertSame(vector, Vector.create(vector, false));
assertEquals(array.length, vector.size());
assertEquals(Double.class, vector.getElementType());
+ assertArrayEquals(array, vector.doubleValues());
+ assertArrayEquals(array, vector.doubleStream().toArray());
/*
* Test element values.
*/
(sis) 04/04: Recognize tabular metadata property values in the JavaFX application as well.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit c372a46be1d550f27abce3e29dc3e40565922038
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Oct 8 13:00:32 2026 +0200
Recognize tabular metadata property values in the JavaFX application as well.
---
.../sis/gui/controls/FormatTableCellFactory.java | 117 +++++++++++++++++++++
.../sis/gui/coverage/ImagePropertyExplorer.java | 16 +--
.../org/apache/sis/gui/internal/PropertyView.java | 43 ++++++--
3 files changed, 161 insertions(+), 15 deletions(-)
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java
new file mode 100644
index 0000000000..bf7b358657
--- /dev/null
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.controls;
+
+import java.text.Format;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import javafx.util.Callback;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.control.TableColumn;
+import org.apache.sis.gui.internal.AlignedTableCell;
+import org.apache.sis.gui.internal.ImmutableObjectProperty;
+
+
+/**
+ * A table cell factory for values formatted using {@code java.text.Format}.
+ * Each table row shall be a {@link List} of objects recognized by the format.
+ * Values are presumed immutable. Instances of this class should be discarded
+ * when the table content change, because it retains references to old values.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <R> the type of value in each row of the table.
+ */
+public final class FormatTableCellFactory<R> implements Function<Object, ObservableValue<String>>,
+ Callback<TableColumn.CellDataFeatures<R, String>, ObservableValue<String>>
+{
+ /**
+ * Creates columns.
+ *
+ * @param <R> the type of values in the column.
+ * @param header header of each column. The length of this array will be the number of columns.
+ * @param formats a supplier of formats for given column indexes.
+ * @return columns to put in the table.
+ */
+ public static <R> TableColumn<R, String>[] createColumns(final String[] header, final IntFunction<Format> formats) {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ final TableColumn<R, String>[] columns = new TableColumn[header.length];
+ final HashMap<Format, Format> previous = HashMap.newHashMap(columns.length);
+ for (int i = 0; i < columns.length; i++) {
+ Format format = formats.apply(i);
+ Format shared = previous.putIfAbsent(format, format);
+ if (shared != null) {
+ format = shared; // We can often use the same format instance for many columns.
+ }
+ final var column = new TableColumn<R, String>(header[i]);
+ column.setCellValueFactory(new FormatTableCellFactory<>(i, format));
+ column.setCellFactory(AlignedTableCell.baselineRight());
+ column.setMinWidth(60);
+ columns[i] = column;
+ }
+ return columns;
+ }
+
+ /**
+ * The column index.
+ */
+ private final int index;
+
+ /**
+ * The format to use for formatting all values in this column.
+ */
+ private final Format format;
+
+ /**
+ * String representations of all formatted values.
+ */
+ private final Map<Object, ObservableValue<String>> values;
+
+ /**
+ * Creates a new factory which will format values using the given format.
+ */
+ private FormatTableCellFactory(final int index, final Format format) {
+ this.index = index;
+ this.format = format;
+ this.values = new HashMap<>();
+ }
+
+ /**
+ * Invoked when a new string representation of a cell is requested.
+ *
+ * @param cell the table cell to format.
+ * @return string representation of the cell value.
+ */
+ @Override
+ public ObservableValue<String> call(final TableColumn.CellDataFeatures<R, String> cell) {
+ return values.computeIfAbsent((cell.getValue() instanceof List<?> row) ? row.get(index) : null, this);
+ }
+
+ /**
+ * Invoked when a new string representation of a value is requested.
+ *
+ * @param value the value to format.
+ * @return string representation of the value.
+ */
+ @Override
+ public ObservableValue<String> apply(final Object value) {
+ return new ImmutableObjectProperty<>(value == null ? null : format.format(value));
+ }
+}
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
index 4bd0ed1597..3f0b209ab7 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
@@ -191,7 +191,7 @@ public class ImagePropertyExplorer extends Widget {
* Creates all rows.
*/
static LayoutRow[] values(final Vocabulary vocabulary, final Resources resources) {
- final LayoutRow[] rows = new LayoutRow[7];
+ final var rows = new LayoutRow[7];
rows[IMAGE_SIZE] = new LayoutRow(true, vocabulary.getString(Vocabulary.Keys.ImageSize));
rows[DISPLAYED_SIZE] = new LayoutRow(false, resources .getString(Resources .Keys.DisplayedSize));
rows[TILE_SIZE] = new LayoutRow(true, vocabulary.getString(Vocabulary.Keys.TileSize));
@@ -402,9 +402,9 @@ public class ImagePropertyExplorer extends Widget {
integerFormat = NumberFormat.getIntegerInstance();
layout.setSelectionModel(null);
- final TableColumn<LayoutRow, String> label = new TableColumn<>(resources.getString(Resources.Keys.SizeOrPosition));
- final TableColumn<LayoutRow, Number> xCol = new TableColumn<>(resources.getString(Resources.Keys.Along_1, "X"));
- final TableColumn<LayoutRow, Number> yCol = new TableColumn<>(resources.getString(Resources.Keys.Along_1, "Y"));
+ final var label = new TableColumn<LayoutRow, String>(resources.getString(Resources.Keys.SizeOrPosition));
+ final var xCol = new TableColumn<LayoutRow, Number>(resources.getString(Resources.Keys.Along_1, "X"));
+ final var yCol = new TableColumn<LayoutRow, Number>(resources.getString(Resources.Keys.Along_1, "Y"));
final Callback<TableColumn<LayoutRow, Number>,
TableCell<LayoutRow, Number>> cellFactory = (column) -> new LayoutCell(integerFormat);
@@ -430,8 +430,8 @@ public class ImagePropertyExplorer extends Widget {
properties = new TableView<>();
propertyRows = properties.getItems();
selectedProperty = properties.getSelectionModel().selectedItemProperty();
- final TableColumn<PropertyRow, String> label = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Property));
- final TableColumn<PropertyRow, Object> value = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Value));
+ final var label = new TableColumn<PropertyRow, String>(vocabulary.getString(Vocabulary.Keys.Property));
+ final var value = new TableColumn<PropertyRow, Object>(vocabulary.getString(Vocabulary.Keys.Value));
label.setCellValueFactory((cell) -> cell.getValue());
value.setCellValueFactory((cell) -> cell.getValue().value);
value.setCellFactory((column) -> new PropertyCell(locale));
@@ -665,7 +665,7 @@ public class ImagePropertyExplorer extends Widget {
/**
* Creates the renderer of cells in the table of image layout information.
*/
- private static final class LayoutCell extends TableCell<LayoutRow,Number> {
+ private static final class LayoutCell extends TableCell<LayoutRow, Number> {
/**
* The formatter to use for numerical values in the table.
*/
@@ -691,7 +691,7 @@ public class ImagePropertyExplorer extends Widget {
/**
* Creates the renderer of cells in the table of image properties.
*/
- private static final class PropertyCell extends TableCell<PropertyRow,Object> {
+ private static final class PropertyCell extends TableCell<PropertyRow, Object> {
/**
* The formatter to use for producing a short string representation of a property value.
*/
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java
index e0d9dee5a5..48af6a092e 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java
@@ -31,6 +31,7 @@ import javafx.scene.Node;
import javafx.scene.text.Font;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
+import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
@@ -41,6 +42,8 @@ import org.opengis.referencing.IdentifiedObject;
import org.apache.sis.math.Statistics;
import org.apache.sis.util.Localized;
import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.internal.shared.TableRowList;
+import org.apache.sis.gui.controls.FormatTableCellFactory;
/**
@@ -81,6 +84,12 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
*/
private ListView<String> listView;
+ /**
+ * Shows the {@linkplain #value} as a table.
+ * This is built only when first needed.
+ */
+ private TableView<Object> tableView;
+
/**
* Shows the {@linkplain #value} as an image.
* This is built only when first needed.
@@ -185,10 +194,11 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
task.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
}
content = switch (newValue) {
- case null -> null;
- case Throwable c -> setText(c);
- case IdentifiedObject c -> setCRS(c);
- case Collection<?> c -> setList(c.toArray());
+ case null -> null;
+ case Throwable c -> setText(c);
+ case IdentifiedObject c -> setCRS(c);
+ case TableRowList<?,?> c -> setTable(c);
+ case Collection<?> c -> setList(c.toArray());
default -> newValue.getClass().isArray() ? setList(newValue)
: setText(formats.formatValue(newValue, true));
};
@@ -233,7 +243,21 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
for (int i=0; i<list.length; i++) {
list[i] = formats.formatValue(Array.get(array, i), true);
}
- listView.getItems().setAll(list);
+ node.getItems().setAll(list);
+ return node;
+ }
+
+ /**
+ * Sets the property value to the given table.
+ */
+ private Node setTable(final TableRowList<?,?> table) {
+ TableView<Object> node = tableView;
+ if (node == null) {
+ node = new TableView<>();
+ tableView = node;
+ }
+ node.getColumns().setAll(FormatTableCellFactory.createColumns(table.columns(), (i) -> table.createFormat(getLocale(), i)));
+ node.getItems().setAll(table);
return node;
}
@@ -264,6 +288,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
* @param boundsChanged whether {@link #visibleImageBounds} changed since last call.
*/
private Node setImage(final RenderedImage image, final boolean boundsChanged) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final Pane imageCanvas = getImageCanvas();
ImageView node = imageView;
if (node == null) {
@@ -295,7 +320,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
imagePane.setHgap(0);
imageView = node;
}
- final ImageConverter converter = new ImageConverter(image, visibleImageBounds, node, imageCanvas);
+ final var converter = new ImageConverter(image, visibleImageBounds, node, imageCanvas);
if (converter.needsRun(boundsChanged)) {
converter.setOnSucceeded((e) -> taskCompleted(converter.getValue()));
converter.setOnFailed((e) -> {
@@ -338,7 +363,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
String mean = null;
if (statistics != null && statistics.length != 0) {
final Statistics s = statistics[0];
- final StringBuffer buffer = new StringBuffer();
+ final var buffer = new StringBuffer();
formats.formatPair(s.minimum(), " … ", s.maximum(), buffer);
range = buffer.toString();
@@ -381,6 +406,10 @@ public final class PropertyView implements Localized, ChangeListener<Number> {
if (listView != null) {
listView.getItems().clear();
}
+ if (tableView != null) {
+ tableView.getItems().clear();
+ tableView.getColumns().clear();
+ }
if (imageView != null) {
ImageConverter.clear(imageView);
}
(sis) 02/04: Add a `TableRowList` internal class and uses it in `PropertyFormat` for better formatting of tabular data.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit d8e23f1abe2faa0083418c759ce9c9c6c1f69b72
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Oct 8 12:55:32 2026 +0200
Add a `TableRowList` internal class and uses it in `PropertyFormat` for better formatting of tabular data.
---
.../apache/sis/util/internal/shared/Numerics.java | 2 +-
.../sis/util/internal/shared/PropertyFormat.java | 66 ++++++++++---
.../sis/util/internal/shared/TableRowList.java | 103 +++++++++++++++++++++
3 files changed, 156 insertions(+), 15 deletions(-)
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
index b1b53c0cdc..7d0e261754 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
@@ -657,7 +657,7 @@ public final class Numerics {
* @return the result of {@code action}.
*/
@Workaround(library="JDK", version="10")
- public static String useScientificNotationIfNeeded(final Format format, final Object value, final BiFunction<Format,Object,String> action) {
+ public static String useScientificNotationIfNeeded(final Format format, final Object value, final BiFunction<Format, Object, String> action) {
if (value instanceof Number && format instanceof DecimalFormat) {
final DecimalFormat df = (DecimalFormat) format;
final int maxFD = df.getMaximumFractionDigits();
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/PropertyFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/PropertyFormat.java
index 50cfe95def..1012173e36 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/PropertyFormat.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/PropertyFormat.java
@@ -24,6 +24,7 @@ import java.util.TimeZone;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
+import java.text.Format;
import org.opengis.util.Type;
import org.opengis.util.Record;
import org.opengis.util.GenericName;
@@ -57,7 +58,23 @@ public abstract class PropertyFormat extends LineAppender implements Localized {
/**
* {@code true} if this method is invoking itself for writing collection values.
*/
- private transient boolean recursive;
+ private boolean recursive;
+
+ /**
+ * Number of collection elements that have already been written.
+ */
+ private byte formattedItemCount;
+
+ /**
+ * Index of the column to format if formating a table.
+ */
+ private int columnIndex;
+
+ /**
+ * Supplier of the formats to use for the next values, or {@code null} if none.
+ * Used for formatting items in a collection.
+ */
+ private TableRowList<?,?> sourceTable;
/**
* Creates a new instance which will write to the given appendable.
@@ -135,7 +152,7 @@ public abstract class PropertyFormat extends LineAppender implements Localized {
appendCollection(Arrays.asList((Object[]) value));
return;
} else if (value instanceof Map.Entry<?,?>) {
- final Map.Entry<?,?> entry = (Map.Entry<?,?>) value;
+ final var entry = (Map.Entry<?,?>) value;
final Object k = entry.getKey();
final Object v = entry.getValue();
if (k == null) {
@@ -149,7 +166,12 @@ public abstract class PropertyFormat extends LineAppender implements Localized {
}
return;
} else {
- text = toString(value);
+ final Format v;
+ if (sourceTable != null && (v = sourceTable.createFormat(getLocale(), columnIndex)) != null) {
+ text = v.format(value);
+ } else {
+ text = toString(value);
+ }
}
append(text);
}
@@ -192,25 +214,41 @@ public abstract class PropertyFormat extends LineAppender implements Localized {
*/
private void appendCollection(final Iterable<?> values) throws IOException {
if (values != null) {
- if (recursive) {
- append('…'); // Do not format collections inside collections.
- } else {
- int count = 0;
+ final var parent = sourceTable;
+ if (values instanceof TableRowList<?,?>) {
+ sourceTable = (TableRowList<?,?>) values;
+ }
+ final boolean isEnclosed = recursive;
+ if (isEnclosed) {
+ append('{');
+ }
+ final int parentIndex = columnIndex;
+ try {
+ columnIndex = 0;
+ recursive = true;
for (final Object value : values) {
if (value != null) {
- if (count != 0) append(", ");
- try {
- recursive = true;
- appendValue(value);
- } finally {
- recursive = false;
+ if (columnIndex != 0) {
+ append(", ");
}
- if (++count == 10) { // Arbitrary limit.
+ appendValue(value);
+ if (++formattedItemCount >= 10) { // Arbitrary limit.
append(", …");
break;
}
+ columnIndex++;
}
}
+ if (isEnclosed) {
+ append('}');
+ }
+ } finally {
+ sourceTable = parent;
+ columnIndex = parentIndex;
+ recursive = isEnclosed;
+ if (!isEnclosed) {
+ formattedItemCount = 0;
+ }
}
}
}
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TableRowList.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TableRowList.java
new file mode 100644
index 0000000000..e39df92d94
--- /dev/null
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TableRowList.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.internal.shared;
+
+import java.util.AbstractList;
+import java.text.Format;
+import java.util.Locale;
+import org.apache.sis.io.TableAppender;
+
+
+/**
+ * A list of row items (for example records) which can be separated in finer items (field values).
+ * This class is not used directly by the utility module, but is defined for allowing transfer of
+ * information between (for example) GeoTIFF and <abbr>GUI</abbr> modules.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <E> type of row items in the list.
+ * @param <V> type of value in columns.
+ */
+public abstract class TableRowList<E, V> extends AbstractList<E> {
+ /**
+ * Creates a new list.
+ */
+ protected TableRowList() {
+ }
+
+ /**
+ * Returns the title of each column, in order.
+ * The length of this array is the number of columns.
+ *
+ * @return the column titles.
+ */
+ public abstract String[] columns();
+
+ /**
+ * Returns the value in the given column of the given row.
+ *
+ * @param row the row, from 0 inclusive to {@link #size()} exclusive.
+ * @param column the column, from 0 inclusive to {@code columns().length} exclusive.
+ * @return value in the given column of the given row.
+ * @throws IndexOutOfBoundsException if the row of column index is invalid.
+ */
+ public abstract V get(int row, int column);
+
+ /**
+ * Returns the format to use for the given column (optional).
+ *
+ * @param locale the locale of the format to create.
+ * @param column the column for which to get a format.
+ * @return format configured for the given column, or {@code null} if none.
+ */
+ public Format createFormat(Locale locale, int column) {
+ return null;
+ }
+
+ /**
+ * Formats this list as a table.
+ *
+ * @return a string representation of this list formatted as a table.
+ */
+ @Override
+ public String toString() {
+ final var locale = Locale.getDefault();
+ final var table = new TableAppender();
+ final String[] columns = columns();
+ final Format[] formats = new Format[columns.length];
+ table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+ table.setMultiLinesCells(true);
+ table.nextLine('═');
+ for (int column = 0; column < columns.length; column++) {
+ formats[column] = createFormat(locale, column);
+ table.append(columns[column]);
+ table.nextColumn();
+ }
+ table.nextLine();
+ table.nextLine('─');
+ final int size = size();
+ for (int row = 0; row < size; row++) {
+ for (int column = 0; column < columns.length; column++) {
+ table.append(formats[column].format(get(row, column)));
+ table.nextColumn();
+ }
+ table.nextLine();
+ }
+ table.nextLine('═');
+ return table.toString();
+ }
+}