diff --git a/.gitignore b/.gitignore
index 469f38c..25e521e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,7 @@ target
pom.xml.*
release.properties
+
+.settings/
+.project/
+.classpath/
diff --git a/README.md b/README.md
index 524db0d..45035e3 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,52 @@ System.out.println(FlipTableConverters.fromObjects(headers, data));
╚════════════╧═══════════╧═════╧═════════╝
```
+Column wrapping (optional)
+--------------------------
+**Fixed Table width**
+```java
+String[] headers = { "First Name", "Last Name", "Details" };
+String[][] data = {
+ { "One One One One", "Two Two Two:Two", "Three Three.Three,Three" },
+ { "Joe", "Smith", "Hello" }
+};
+System.out.println(FlipTable.of(headers, data, FixedWidth.withWidth(30)));
+
+```
+```
+╔════════════╤═══════════╤═════════════╗
+║ First Name │ Last Name │ Details ║
+╠════════════╪═══════════╪═════════════╣
+║ One One │ Two Two │ Three Three ║
+║ One One │ Two:Two │ .Three, ║
+║ │ │ Three ║
+╟────────────┼───────────┼─────────────╢
+║ Joe │ Smith │ Hello ║
+╚════════════╧═══════════╧═════════════╝
+```
+
+**Custom Column widths**
+```java
+String[] headers = { "First", "Last", "Det" };
+String[][] data = {
+ { "One One One One", "Two Two Two:Two", "Fifteen four on on on on on five" },
+ { "Joe", "Boe", "Hello" }
+};
+System.out.println(FlipTable.of(headers, data, CustomColumnWidth.withColumnWidths(new int[] {5, 5, 8})));
+```
+```
+╔═══════╤═══════╤══════════╗
+║ First │ Last │ Det ║
+╠═══════╪═══════╪══════════╣
+║ One │ Two │ Fifteen ║
+║ One │ Two │ four on ║
+║ One │ Two: │ on on on ║
+║ One │ Two │ on five ║
+╟───────┼───────┼──────────╢
+║ Joe │ Boe │ Hello ║
+╚═══════╧═══════╧══════════╝
+```
Download
--------
diff --git a/pom.xml b/pom.xml
index bf1c695..999fe1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,8 +68,8 @@
* ╔═════════════╤════════════════════════════╤══════════════╗
@@ -30,10 +34,13 @@ public final class FlipTable {
/** Create a new table with the specified headers and row data. */
public static String of(String[] headers, String[][] data) {
+ return of(headers, data, new DefaultWrapFormat());
+ }
+ public static String of(String[] headers, String[][] data, ColumnWrapFormat columnWrapFormat) {
if (headers == null) throw new NullPointerException("headers == null");
if (headers.length == 0) throw new IllegalArgumentException("Headers must not be empty.");
if (data == null) throw new NullPointerException("data == null");
- return new FlipTable(headers, data).toString();
+ return new FlipTable(headers, data, columnWrapFormat).toString();
}
private final String[] headers;
@@ -42,12 +49,10 @@ public static String of(String[] headers, String[][] data) {
private final int[] columnWidths;
private final int emptyWidth;
- private FlipTable(String[] headers, String[][] data) {
- this.headers = headers;
- this.data = data;
-
+ private FlipTable(String[] headers, String[][] data, ColumnWrapFormat columnWrapFormat) {
+
columns = headers.length;
- columnWidths = new int[columns];
+ int[] columnWidths = new int[columns];
for (int row = -1; row < data.length; row++) {
String[] rowData = (row == -1) ? headers : data[row]; // Hack to parse headers too.
if (rowData.length != columns) {
@@ -61,6 +66,11 @@ private FlipTable(String[] headers, String[][] data) {
}
}
}
+
+ WrappedTableData wrappedTableData = columnWrapFormat.adjustData(headers, data, columnWidths);
+ this.headers = wrappedTableData.getHeaders();
+ this.data = wrappedTableData.getData();
+ this.columnWidths = wrappedTableData.getColumnWidths();
int emptyWidth = 3 * (columns - 1); // Account for column dividers and their spacing.
for (int columnWidth : columnWidths) {
diff --git a/src/main/java/com/jakewharton/fliptables/format/wrap/ColumnWrapFormat.java b/src/main/java/com/jakewharton/fliptables/format/wrap/ColumnWrapFormat.java
new file mode 100644
index 0000000..555a68b
--- /dev/null
+++ b/src/main/java/com/jakewharton/fliptables/format/wrap/ColumnWrapFormat.java
@@ -0,0 +1,53 @@
+package com.jakewharton.fliptables.format.wrap;
+
+/**
+ * Provides a mechanism to wrap the text of the columns.
+ * Line breaks are added to break long texts without breaking the words
+ */
+public abstract class ColumnWrapFormat {
+
+ private static final String SPLITTER_REGEX = "((?=:|,|\\.|\\s)|(?<=:|,|\\.|\\s))";
+ private static final String LINE_BREAK = "\n";
+
+ public abstract WrappedTableData adjustData(String[] headers, String[][] data, int[] columnWidths);
+
+ protected String[][] adjustedData(String[][] data, int[] adjustedWidths) {
+ String[][] adjustedData = new String[data.length][adjustedWidths.length];
+ for(int row = 0; row < data.length; row++) {
+ for(int col = 0; col < adjustedWidths.length; col++) {
+ adjustedData[row][col] = adjustFieldData(data[row][col], adjustedWidths[col]);
+ }
+ }
+ return adjustedData;
+ }
+
+ protected String adjustFieldData(String field, int desiredWidth) {
+ if(null == field || field.isEmpty() || field.length() <= desiredWidth) {
+ return field;
+ }
+ return withLineBreaks(field, desiredWidth);
+ }
+
+ /**
+ * Adds line breaks on maxLineLength boundaries without breaking the words
+ */
+ private String withLineBreaks(String input, int maxLineLength) {
+ String[] components = input.split(SPLITTER_REGEX);
+ StringBuilder builder = new StringBuilder();
+ int lineLength = 0, iterator = 0;
+ do {
+ String component = components[iterator];
+ if(lineLength == 0 || (lineLength + component.length() <= maxLineLength)) {
+ builder.append(component);
+ lineLength += component.length();
+ iterator++;
+ }
+ else {
+ builder.append(LINE_BREAK);
+ lineLength = 0;
+ }
+ } while(iterator < components.length);
+
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/jakewharton/fliptables/format/wrap/CustomColumnWidth.java b/src/main/java/com/jakewharton/fliptables/format/wrap/CustomColumnWidth.java
new file mode 100644
index 0000000..bed8b72
--- /dev/null
+++ b/src/main/java/com/jakewharton/fliptables/format/wrap/CustomColumnWidth.java
@@ -0,0 +1,31 @@
+package com.jakewharton.fliptables.format.wrap;
+
+/**
+ * Adjusting the columns of the table to the user provided column widths
+ * A column is never made narrower than the length of the header
+ */
+public class CustomColumnWidth extends ColumnWrapFormat {
+
+ private int[] desiredColumnWidths;
+
+ public static CustomColumnWidth withColumnWidths(int[] desiredColumnWidths) {
+ return new CustomColumnWidth(desiredColumnWidths);
+ }
+
+ private CustomColumnWidth(int[] desiredColumnWidths) {
+ this.desiredColumnWidths = desiredColumnWidths;
+ }
+
+ @Override
+ public WrappedTableData adjustData(String[] headers, String[][] data, int[] columnWidths) {
+ if (desiredColumnWidths.length != headers.length) {
+ throw new IllegalArgumentException("Length of the array of the desired columns does not match the number of columns");
+ }
+
+ int[] adjustedWidths = new int[columnWidths.length];
+ for(int col = 0; col < columnWidths.length; col++) {
+ adjustedWidths[col] = Math.max(headers[col].length(), desiredColumnWidths[col]);
+ }
+ return new WrappedTableData(headers, adjustedData(data, adjustedWidths), adjustedWidths);
+ }
+}
diff --git a/src/main/java/com/jakewharton/fliptables/format/wrap/DefaultWrapFormat.java b/src/main/java/com/jakewharton/fliptables/format/wrap/DefaultWrapFormat.java
new file mode 100644
index 0000000..cdd5d59
--- /dev/null
+++ b/src/main/java/com/jakewharton/fliptables/format/wrap/DefaultWrapFormat.java
@@ -0,0 +1,9 @@
+package com.jakewharton.fliptables.format.wrap;
+
+public class DefaultWrapFormat extends ColumnWrapFormat {
+
+ @Override
+ public WrappedTableData adjustData(String[] headers, String[][] data, int[] columnWidths) {
+ return new WrappedTableData(headers, data, columnWidths);
+ }
+}
diff --git a/src/main/java/com/jakewharton/fliptables/format/wrap/FixedWidth.java b/src/main/java/com/jakewharton/fliptables/format/wrap/FixedWidth.java
new file mode 100644
index 0000000..afd042e
--- /dev/null
+++ b/src/main/java/com/jakewharton/fliptables/format/wrap/FixedWidth.java
@@ -0,0 +1,43 @@
+package com.jakewharton.fliptables.format.wrap;
+
+import java.util.Arrays;
+
+/**
+ * Reduces the individual column widths proportional to the table width.
+ * A column is never made narrower than the length of the header
+ */
+public class FixedWidth extends ColumnWrapFormat {
+
+ private int maxWidth;
+
+ public static FixedWidth withWidth(int tableWidth) {
+ return new FixedWidth(tableWidth);
+ }
+
+ private FixedWidth(int width) {
+ this.maxWidth = width;
+ }
+
+ @Override
+ public WrappedTableData adjustData(String[] headers, String[][] data, int[] columnWidths) {
+ int currentLength = Arrays.stream(columnWidths).sum();
+ double ratio = maxWidth / (1.0 * currentLength);
+ if(ratio > 1.0d) {
+ return new WrappedTableData(headers, data, columnWidths);
+ }
+ int[] adjustedWidths = adjustedColumnWidths(headers, columnWidths, ratio);
+ return new WrappedTableData(headers, adjustedData(data, adjustedWidths), adjustedWidths);
+ }
+
+ private int[] adjustedColumnWidths(String headers[], int[] columnWidths, double ratio) {
+ int[] adjustedWidths = headers.length == 1 ? new int[] { maxWidth }: new int[headers.length];
+ int index = 0, sum = 0;
+ for(; index < headers.length - 1; index++) {
+ adjustedWidths[index] = Math.max((int) (ratio * columnWidths[index]), headers[index].length());
+ sum += adjustedWidths[index];
+ }
+ adjustedWidths[index] = Math.max(maxWidth - sum, headers[index].length());
+ return adjustedWidths;
+ }
+
+}
diff --git a/src/main/java/com/jakewharton/fliptables/format/wrap/WrappedTableData.java b/src/main/java/com/jakewharton/fliptables/format/wrap/WrappedTableData.java
new file mode 100644
index 0000000..ee91c12
--- /dev/null
+++ b/src/main/java/com/jakewharton/fliptables/format/wrap/WrappedTableData.java
@@ -0,0 +1,30 @@
+package com.jakewharton.fliptables.format.wrap;
+
+/**
+ * Class to hold the adjusted (wrapped) data out
+ */
+public class WrappedTableData {
+ String[] headers;
+ String[][] data;
+ int[] columnWidths;
+
+ public WrappedTableData() {}
+
+ public WrappedTableData(String[] headers, String[][] data, int[] columnWidths) {
+ this.headers = headers;
+ this.data = data;
+ this.columnWidths = columnWidths;
+ }
+
+ public String[] getHeaders() {
+ return this.headers;
+ }
+
+ public String[][] getData() {
+ return this.data;
+ }
+
+ public int[] getColumnWidths() {
+ return this.columnWidths;
+ }
+}
diff --git a/src/test/java/com/jakewharton/fliptables/format/wrap/ColumnWrapTest.java b/src/test/java/com/jakewharton/fliptables/format/wrap/ColumnWrapTest.java
new file mode 100644
index 0000000..57d3d66
--- /dev/null
+++ b/src/test/java/com/jakewharton/fliptables/format/wrap/ColumnWrapTest.java
@@ -0,0 +1,58 @@
+package com.jakewharton.fliptables.format.wrap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.junit.Test;
+
+import com.jakewharton.fliptables.FlipTable;
+import com.jakewharton.fliptables.util.ResourceReader;
+
+public class ColumnWrapTest {
+
+ private String[] headers = new String[] {"Feather", "Weather", "Fizz", "Buzz"};
+ private String[][] data = new String[3][headers.length];
+
+ public ColumnWrapTest() {
+ for(int idx = 0; idx < 3; idx++) {
+ data[idx][0] = columnData("a", 6, 10);
+ data[idx][1] = columnData("b", 6, 5);
+ data[idx][2] = columnData("c", 6, 15);
+ data[idx][3] = columnData("d", 6, 25);
+ }
+ data[1][2] = "some long words somelongwords somelongerwords someevenlongerwords someevenmorelongerwords";
+ }
+
+ private static String columnData(String base, int wordSize, int words) {
+ return IntStream.range(0, words)
+ .mapToObj(wordIndex -> IntStream.range(0, wordSize).mapToObj(charIndex -> base).collect(Collectors.joining()))
+ .collect(Collectors.joining(" "));
+ }
+
+ @Test
+ public void testFixedWidthWrapping() throws IOException {
+ String read = ResourceReader.readFromResourceFile("fixed-width-wrapping.txt");
+ String flipTable = FlipTable.of(headers, data, FixedWidth.withWidth(120));
+ assertThat(flipTable).isEqualTo(read);
+ }
+
+ @Test
+ public void testCustomWidthWrapping() throws IOException {
+ String read = ResourceReader.readFromResourceFile("custom-width-wrapping.txt");
+ String flipTable = FlipTable.of(headers, data, CustomColumnWidth.withColumnWidths(new int[] {30, 30, 30, 30}));
+ assertThat(flipTable).isEqualTo(read);
+ }
+
+ public static void main(String args[]) {
+ String[] headers = { "First", "Last", "Det" };
+ String[][] data = {
+ { "One One One One", "Two Two Two:Two", "Fifteen four on on on on on five" },
+ { "Joe", "Boe", "Hello" }
+ };
+ System.out.println(FlipTable.of(headers, data, CustomColumnWidth.withColumnWidths(new int[] {5, 5, 8})));
+ }
+
+}
diff --git a/src/test/java/com/jakewharton/fliptables/util/ResourceReader.java b/src/test/java/com/jakewharton/fliptables/util/ResourceReader.java
new file mode 100644
index 0000000..64193ab
--- /dev/null
+++ b/src/test/java/com/jakewharton/fliptables/util/ResourceReader.java
@@ -0,0 +1,26 @@
+package com.jakewharton.fliptables.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class ResourceReader {
+
+ public static String readFromResourceFile(String filePath) throws IOException {
+
+ try(InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath)) {
+ StringBuilder textBuilder = new StringBuilder();
+ try (Reader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
+ int c = 0;
+ while ((c = reader.read()) != -1) {
+ textBuilder.append((char) c);
+ }
+ }
+ return textBuilder.toString();
+ }
+ }
+}
diff --git a/src/test/resources/custom-width-wrapping.txt b/src/test/resources/custom-width-wrapping.txt
new file mode 100644
index 0000000..7f7055d
--- /dev/null
+++ b/src/test/resources/custom-width-wrapping.txt
@@ -0,0 +1,27 @@
+╔════════════════════════════════╤════════════════════════════════╤════════════════════════════════╤════════════════════════════════╗
+║ Feather │ Weather │ Fizz │ Buzz ║
+╠════════════════════════════════╪════════════════════════════════╪════════════════════════════════╪════════════════════════════════╣
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb bbbbbb bbbbbb bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa │ │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ │ │ cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd ║
+╟────────────────────────────────┼────────────────────────────────┼────────────────────────────────┼────────────────────────────────╢
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb bbbbbb bbbbbb bbbbbb │ some long words somelongwords │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb │ somelongerwords │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa │ │ someevenlongerwords │ dddddd dddddd dddddd dddddd ║
+║ │ │ someevenmorelongerwords │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd ║
+╟────────────────────────────────┼────────────────────────────────┼────────────────────────────────┼────────────────────────────────╢
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb bbbbbb bbbbbb bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa │ │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ │ │ cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd dddddd dddddd dddddd ║
+║ │ │ │ dddddd ║
+╚════════════════════════════════╧════════════════════════════════╧════════════════════════════════╧════════════════════════════════╝
diff --git a/src/test/resources/fixed-width-wrapping.txt b/src/test/resources/fixed-width-wrapping.txt
new file mode 100644
index 0000000..611c55e
--- /dev/null
+++ b/src/test/resources/fixed-width-wrapping.txt
@@ -0,0 +1,21 @@
+╔═══════════════════════╤════════════╤══════════════════════════════════╤═══════════════════════════════════════════════════════════╗
+║ Feather │ Weather │ Fizz │ Buzz ║
+╠═══════════════════════╪════════════╪══════════════════════════════════╪═══════════════════════════════════════════════════════════╣
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa │ bbbbbb │ cccccc cccccc cccccc │ dddddd ║
+║ │ bbbbbb │ │ ║
+╟───────────────────────┼────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────╢
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ some long words somelongwords │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ somelongerwords │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ someevenlongerwords │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa │ bbbbbb │ someevenmorelongerwords │ dddddd ║
+║ │ bbbbbb │ │ ║
+╟───────────────────────┼────────────┼──────────────────────────────────┼───────────────────────────────────────────────────────────╢
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa aaaaaa aaaaaa │ bbbbbb │ cccccc cccccc cccccc cccccc │ dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ║
+║ aaaaaa │ bbbbbb │ cccccc cccccc cccccc │ dddddd ║
+║ │ bbbbbb │ │ ║
+╚═══════════════════════╧════════════╧══════════════════════════════════╧═══════════════════════════════════════════════════════════╝