6
pom.xml
6
pom.xml
@@ -29,6 +29,7 @@
|
|||||||
<module>spring-shell-shell1-adapter</module>
|
<module>spring-shell-shell1-adapter</module>
|
||||||
<module>spring-shell-starter</module>
|
<module>spring-shell-starter</module>
|
||||||
<module>spring-shell-docs</module>
|
<module>spring-shell-docs</module>
|
||||||
|
<module>spring-shell-table</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -69,6 +70,11 @@
|
|||||||
<artifactId>spring-shell-starter</artifactId>
|
<artifactId>spring-shell-starter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.shell</groupId>
|
||||||
|
<artifactId>spring-shell-table</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- OTHER DEPS -->
|
<!-- OTHER DEPS -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jline</groupId>
|
<groupId>org.jline</groupId>
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jline</groupId>
|
<groupId>org.jline</groupId>
|
||||||
<artifactId>jline</artifactId>
|
<artifactId>jline</artifactId>
|
||||||
@@ -36,6 +31,19 @@
|
|||||||
<groupId>org.jline</groupId>
|
<groupId>org.jline</groupId>
|
||||||
<artifactId>jline-terminal-jna</artifactId>
|
<artifactId>jline-terminal-jna</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.shell</groupId>
|
||||||
|
<artifactId>spring-shell-table</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ package org.springframework.shell.result;
|
|||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.shell.ResultHandler;
|
import org.springframework.shell.ResultHandler;
|
||||||
|
import org.springframework.shell.TerminalSizeAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for explicit configuration of {@link org.springframework.shell.ResultHandler}s.
|
* Used for explicit configuration of {@link org.springframework.shell.ResultHandler}s.
|
||||||
@@ -47,4 +49,10 @@ public class ResultHandlerConfig {
|
|||||||
iterableResultHandler().setDelegate(mainResultHandler());
|
iterableResultHandler().setDelegate(mainResultHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(TerminalSizeAware.class)
|
||||||
|
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler() {
|
||||||
|
return new TerminalSizeAwareResultHandler();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.result;
|
||||||
|
|
||||||
|
import org.springframework.shell.ResultHandler;
|
||||||
|
import org.springframework.shell.TerminalSizeAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ResultHandler that prints {@link TerminalSizeAware} according to the {@link org.jline.terminal.Terminal} size.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class TerminalSizeAwareResultHandler extends TerminalAwareResultHandler implements ResultHandler<TerminalSizeAware> {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResult(TerminalSizeAware result) {
|
||||||
|
CharSequence toPrint = result.render(terminal.getWidth());
|
||||||
|
terminal.writer().println(toPrint);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.samples.standard;
|
||||||
|
|
||||||
|
import org.springframework.shell.standard.ShellComponent;
|
||||||
|
import org.springframework.shell.standard.ShellMethod;
|
||||||
|
import org.springframework.shell.table.*;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@ShellComponent
|
||||||
|
public class TableCommands {
|
||||||
|
|
||||||
|
private static final String TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
|
||||||
|
"ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
|
||||||
|
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
|
||||||
|
"voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " +
|
||||||
|
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
||||||
|
|
||||||
|
@ShellMethod("Showcase Table rendering")
|
||||||
|
public Table table() {
|
||||||
|
String[][] data = new String[3][3];
|
||||||
|
TableModel model = new ArrayTableModel(data);
|
||||||
|
TableBuilder tableBuilder = new TableBuilder(model);
|
||||||
|
|
||||||
|
Random r = new Random();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
data[i][j] = TEXT.substring(0, TEXT.length() / 2 + r.nextInt(TEXT.length() / 2));
|
||||||
|
tableBuilder.on(at(i, j)).addAligner(SimpleHorizontalAligner.values()[j]);
|
||||||
|
tableBuilder.on(at(i, j)).addAligner(SimpleVerticalAligner.values()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableBuilder.addFullBorder(BorderStyle.fancy_light).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CellMatcher at(final int theRow, final int col) {
|
||||||
|
return new CellMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
return row == theRow && column == col;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
<groupId>org.springframework.shell</groupId>
|
<groupId>org.springframework.shell</groupId>
|
||||||
<artifactId>spring-shell-jcommander-adapter</artifactId>
|
<artifactId>spring-shell-jcommander-adapter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.shell</groupId>
|
||||||
|
<artifactId>spring-shell-table</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
40
spring-shell-table/pom.xml
Normal file
40
spring-shell-table/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright 2017 the original author or authors.
|
||||||
|
~
|
||||||
|
~ Licensed 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>spring-shell-table</artifactId>
|
||||||
|
<name>Spring Shell Table</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.shell</groupId>
|
||||||
|
<artifactId>spring-shell-parent</artifactId>
|
||||||
|
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<description>Library to Display Fancy ASCII art Tables</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be implemented by command result objects that can adapt to the terminal size when they are being rendered.
|
||||||
|
*
|
||||||
|
* <p>An object which does not implement this interface will simply be rendered by invoking its {@link Object#toString()}
|
||||||
|
* method.</p>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface TerminalSizeAware {
|
||||||
|
|
||||||
|
CharSequence render(int terminalWidth);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell sizing strategy that forces a fixed width, expressed in number of characters.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class AbsoluteWidthSizeConstraints implements SizeConstraints {
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
|
||||||
|
public AbsoluteWidthSizeConstraints(int width) {
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Extent width(String[] raw, int previous, int tableWidth) {
|
||||||
|
return new Extent(width, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A strategy interface for performing text alignment.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface Aligner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform text alignment, returning a String array that MUST contain {@code cellHeight}
|
||||||
|
* lines, each of which MUST be {@code cellWidth} chars in length.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Input array is guaranteed to contain lines that have length equal to {@code cellWidth}.
|
||||||
|
* There is no guarantee on the input number of lines though.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param text the text to align
|
||||||
|
* @param cellWidth the width of of the table cell
|
||||||
|
* @param cellHeight the height of the table cell
|
||||||
|
* @return the aligned text, in a {@code cellHeight} element array
|
||||||
|
*/
|
||||||
|
String[] align(String[] text, int cellWidth, int cellHeight);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TableModel backed by a row-first array.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class ArrayTableModel extends TableModel {
|
||||||
|
|
||||||
|
private Object[][] data;
|
||||||
|
|
||||||
|
public ArrayTableModel(Object[][] data) {
|
||||||
|
this.data = data;
|
||||||
|
int width = data.length > 0 ? data[0].length : 0;
|
||||||
|
for (int row = 0; row < data.length; row++) {
|
||||||
|
Assert.isTrue(width == data[row].length, "All rows of array data must be of same length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRowCount() {
|
||||||
|
return data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumnCount() {
|
||||||
|
return data.length > 0 ? data[0].length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue(int row, int column) {
|
||||||
|
return data[row][column];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SizeConstraints implementation that splits lines at space boundaries
|
||||||
|
* and returns an extent with minimal and maximal width requirements.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class AutoSizeConstraints implements SizeConstraints {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||||
|
int max = 0;
|
||||||
|
int min = 0;
|
||||||
|
for (String line : raw) {
|
||||||
|
String[] words = line.split(" ");
|
||||||
|
for (String word : words) {
|
||||||
|
min = Math.max(min, word.length());
|
||||||
|
}
|
||||||
|
max = Math.max(max, line.length());
|
||||||
|
}
|
||||||
|
return new Extent(min, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.BeanWrapper;
|
||||||
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A table model that is backed by a list of beans.
|
||||||
|
*
|
||||||
|
* <p>One can control which properties are exposed (and their order). There is also
|
||||||
|
* a convenience constructor for adding a special header row.</p>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class BeanListTableModel<T> extends TableModel {
|
||||||
|
|
||||||
|
private final List<BeanWrapper> data;
|
||||||
|
|
||||||
|
private final List<String> propertyNames;
|
||||||
|
|
||||||
|
private final List<Object> headerRow;
|
||||||
|
|
||||||
|
public BeanListTableModel(Class<T> clazz, Iterable<T> list) {
|
||||||
|
this.data = new ArrayList<BeanWrapper>();
|
||||||
|
for (T bean : list) {
|
||||||
|
this.data.add(new BeanWrapperImpl(bean));
|
||||||
|
}
|
||||||
|
this.headerRow = null;
|
||||||
|
propertyNames = new ArrayList<String>();
|
||||||
|
for (PropertyDescriptor propertyName : BeanUtils.getPropertyDescriptors(clazz)) {
|
||||||
|
if ("class".equals(propertyName.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
propertyNames.add(propertyName.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeanListTableModel(Iterable<T> list, String... propertyNames) {
|
||||||
|
this.data = new ArrayList<BeanWrapper>();
|
||||||
|
for (T bean : list) {
|
||||||
|
this.data.add(new BeanWrapperImpl(bean));
|
||||||
|
}
|
||||||
|
this.headerRow = null;
|
||||||
|
this.propertyNames = Arrays.asList(propertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeanListTableModel(Iterable<T> list, LinkedHashMap<String, Object> header) {
|
||||||
|
this.data = new ArrayList<BeanWrapper>();
|
||||||
|
for (T bean : list) {
|
||||||
|
this.data.add(new BeanWrapperImpl(bean));
|
||||||
|
}
|
||||||
|
this.headerRow = new ArrayList<Object>(header.values());
|
||||||
|
propertyNames = new ArrayList<String>(header.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return headerRow == null ? data.size() : 1 + data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return propertyNames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue(int row, int column) {
|
||||||
|
if (headerRow != null && row == 0) {
|
||||||
|
return headerRow.get(column);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int rowToUse = headerRow == null ? row : row - 1;
|
||||||
|
String propertyName = propertyNames.get(column);
|
||||||
|
return data.get(rowToUse).getPropertyValue(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a directive to set some borders on cells of a table.
|
||||||
|
* Multiple specifications can be combined on a single table.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class BorderSpecification {
|
||||||
|
|
||||||
|
public static final int NONE = 0;
|
||||||
|
|
||||||
|
public static final int TOP = 1;
|
||||||
|
|
||||||
|
public static final int BOTTOM = 2;
|
||||||
|
|
||||||
|
public static final int LEFT = 4;
|
||||||
|
|
||||||
|
public static final int RIGHT = 8;
|
||||||
|
|
||||||
|
public static final int INNER_VERTICAL = 16;
|
||||||
|
|
||||||
|
public static final int INNER_HORIZONTAL = 32;
|
||||||
|
|
||||||
|
public static final int OUTLINE = TOP | BOTTOM | LEFT | RIGHT;
|
||||||
|
|
||||||
|
public static final int FULL = OUTLINE | INNER_HORIZONTAL | INNER_VERTICAL;
|
||||||
|
|
||||||
|
public static final int INNER = INNER_HORIZONTAL | INNER_VERTICAL;
|
||||||
|
|
||||||
|
private final int row1, row2, column1, column2;
|
||||||
|
|
||||||
|
private final int match;
|
||||||
|
|
||||||
|
private final BorderStyle style;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifications are created by {@link Table#addBorder(int, int, int, int, int, BorderStyle)}.
|
||||||
|
*/
|
||||||
|
/*default*/ BorderSpecification(int row1, int column1, int row2, int column2, int match, BorderStyle style) {
|
||||||
|
this.row1 = row1;
|
||||||
|
this.row2 = row2;
|
||||||
|
this.column1 = column1;
|
||||||
|
this.column2 = column2;
|
||||||
|
this.match = match;
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this specification result in the need to paint a vertical bar at row,column?
|
||||||
|
*/
|
||||||
|
/*default*/ char verticals(int row, int column) {
|
||||||
|
boolean result = (match & LEFT) == LEFT && column == column1;
|
||||||
|
result |= (match & INNER_VERTICAL) == INNER_VERTICAL && column > column1 && column < column2;
|
||||||
|
result |= (match & RIGHT) == RIGHT && column == column2;
|
||||||
|
|
||||||
|
result &= row >= row1;
|
||||||
|
result &= row < row2;
|
||||||
|
return result ? style.verticalGlyph() : BorderStyle.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this specification result in the need to paint an horizontal bar at row,column?
|
||||||
|
*/
|
||||||
|
/*default*/ char horizontals(int row, int column) {
|
||||||
|
boolean result = (match & TOP) == TOP && row == row1;
|
||||||
|
result |= (match & INNER_HORIZONTAL) == INNER_HORIZONTAL && row > row1 && row < row2;
|
||||||
|
result |= (match & BOTTOM) == BOTTOM && row == row2;
|
||||||
|
|
||||||
|
result &= column >= column1;
|
||||||
|
result &= column < column2;
|
||||||
|
return result ? style.horizontalGlyph() : BorderStyle.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s[(%d, %d)->(%d, %d), %s, %s]", getClass().getSimpleName(), row1, column1, row2, column2, style, matchConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String matchConstants() {
|
||||||
|
try {
|
||||||
|
for (String field : new String[] {"NONE", "INNER", "FULL", "OUTLINE"}) {
|
||||||
|
int value = ReflectionUtils.findField(getClass(), field).getInt(null);
|
||||||
|
if (match == value) {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> constants = new ArrayList<String>();
|
||||||
|
for (String field : new String[] {"TOP", "BOTTOM", "LEFT", "RIGHT", "INNER_HORIZONTAL", "INNER_VERTICAL"}) {
|
||||||
|
int value = ReflectionUtils.findField(getClass(), field).getInt(null);
|
||||||
|
if ((match & value) == value) {
|
||||||
|
constants.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.collectionToDelimitedString(constants, "|");
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides support for different styles of borders, using simple or fancy ascii art.
|
||||||
|
*
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Box-drawing_character">https://en.wikipedia.org/wiki/Box-drawing_character</a>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public enum BorderStyle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simplistic style, using characters that ought to always be available in all systems (pipe and minus).
|
||||||
|
*/
|
||||||
|
oldschool('|', '-'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated light box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_light('│', '─'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated fat box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_heavy('┃', '━'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double-light box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_double('║', '═'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses space characters, giving some space between columns.
|
||||||
|
*/
|
||||||
|
air(' ', ' '),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_light_double_dash('╎', '╌'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_light_triple_dash('┆', '┄'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_light_quadruple_dash('┊', '┈'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_heavy_double_dash('╏', '╍'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_heavy_triple_dash('┇', '┅'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||||
|
*/
|
||||||
|
fancy_heavy_quadruple_dash('┋', '┉'),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private char vertical;
|
||||||
|
|
||||||
|
private char horizontal;
|
||||||
|
|
||||||
|
public static final char NONE = '\u0000';
|
||||||
|
|
||||||
|
private static Map<Long, Character> CORNERS = new HashMap<Long, Character>();
|
||||||
|
|
||||||
|
private static Map<Character, Character> EQUIVALENTS = new HashMap<Character, Character>();
|
||||||
|
|
||||||
|
public char verticalGlyph() {
|
||||||
|
return vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char horizontalGlyph() {
|
||||||
|
return horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
registerCorners("─│┌┐└┘├┤┬┴┼");
|
||||||
|
registerCorners("━┃┏┓┗┛┣┫┳┻╋");
|
||||||
|
|
||||||
|
// double dashes
|
||||||
|
registerCorners("╌╎┌┐└┘├┤┬┴┼");
|
||||||
|
registerCorners("╍╏┏┓┗┛┣┫┳┻╋");
|
||||||
|
|
||||||
|
// triple dashes
|
||||||
|
registerCorners("┈┆┌┐└┘├┤┬┴┼");
|
||||||
|
registerCorners("┅┇┏┓┗┛┣┫┳┻╋");
|
||||||
|
|
||||||
|
// quad dashes
|
||||||
|
registerCorners("┈┊┌┐└┘├┤┬┴┼");
|
||||||
|
registerCorners("┉┋┏┓┗┛┣┫┳┻╋");
|
||||||
|
|
||||||
|
// double lines
|
||||||
|
registerCorners("═║╔╗╚╝╠╣╦╩╬");
|
||||||
|
// oldschool
|
||||||
|
registerCorners("-|+++++++++");
|
||||||
|
// air style
|
||||||
|
registerCorners(" ");
|
||||||
|
|
||||||
|
// Register some mixed-style combinations
|
||||||
|
// light + heavy
|
||||||
|
registerCorner('│', '│', '━', NONE, '┥');
|
||||||
|
registerCorner('│', '│', NONE, '━', '┝');
|
||||||
|
registerCorner('┃', NONE, '─', '─', '┸');
|
||||||
|
registerCorner(NONE, '┃', '─', '─', '┰');
|
||||||
|
// heavy + light
|
||||||
|
registerCorner('┃', '┃', '─', NONE, '┨');
|
||||||
|
registerCorner('┃', '┃', NONE, '─', '┠');
|
||||||
|
registerCorner('│', NONE, '━', '━', '┷');
|
||||||
|
registerCorner(NONE, '│', '━', '━', '┯');
|
||||||
|
// double + single
|
||||||
|
registerCorner('║', '║', '─', NONE, '╢');
|
||||||
|
registerCorner('║', '║', NONE, '─', '╟');
|
||||||
|
registerCorner('│', NONE, '═', '═', '╧');
|
||||||
|
registerCorner(NONE, '│', '═', '═', '╤');
|
||||||
|
// single + double
|
||||||
|
registerCorner('│', '│', '═', NONE, '╡');
|
||||||
|
registerCorner('│', '│', NONE, '═', '╞');
|
||||||
|
registerCorner('║', NONE, '─', '─', '╨');
|
||||||
|
registerCorner(NONE, '║', '─', '─', '╥');
|
||||||
|
// heavy + light, 90°
|
||||||
|
registerCorner('┃', '│', '━', '─', '╃');
|
||||||
|
registerCorner('│', '┃', '─', '━', '╆');
|
||||||
|
registerCorner('┃', '│', '─', '━', '╄');
|
||||||
|
registerCorner('│', '┃', '━', '─', '╅');
|
||||||
|
// light crossing (heavy or double)
|
||||||
|
registerCorner('│', '│', '━', '━', '┿');
|
||||||
|
registerCorner('│', '│', '═', '═', '╪');
|
||||||
|
registerCorner('┃', '┃', '─', '─', '╂');
|
||||||
|
registerCorner('║', '║', '─', '─', '╫');
|
||||||
|
|
||||||
|
// Dashed variants crossing others behave like regular corners
|
||||||
|
registerSameCorners(fancy_light_double_dash, fancy_light);
|
||||||
|
registerSameCorners(fancy_light_triple_dash, fancy_light);
|
||||||
|
registerSameCorners(fancy_light_quadruple_dash, fancy_light);
|
||||||
|
registerSameCorners(fancy_heavy_double_dash, fancy_heavy);
|
||||||
|
registerSameCorners(fancy_heavy_triple_dash, fancy_heavy);
|
||||||
|
registerSameCorners(fancy_heavy_quadruple_dash, fancy_heavy);
|
||||||
|
|
||||||
|
|
||||||
|
// Air-style glyphs are easy to combine with others. Register some combinations
|
||||||
|
registerMixedWithAirCombinations(oldschool.vertical, oldschool.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_light.vertical, fancy_light.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_double.vertical, fancy_double.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_heavy.vertical, fancy_heavy.horizontal);
|
||||||
|
|
||||||
|
registerMixedWithAirCombinations(fancy_light_double_dash.vertical, fancy_light_double_dash.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_light_triple_dash.vertical, fancy_light_triple_dash.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_light_quadruple_dash.vertical, fancy_light_quadruple_dash.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_heavy_double_dash.vertical, fancy_heavy_double_dash.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_heavy_triple_dash.vertical, fancy_heavy_triple_dash.horizontal);
|
||||||
|
registerMixedWithAirCombinations(fancy_heavy_quadruple_dash.vertical, fancy_heavy_quadruple_dash.horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the fact that for corner purposes, style1 behaves like style2.
|
||||||
|
*/
|
||||||
|
private static void registerSameCorners(BorderStyle style1, BorderStyle style2) {
|
||||||
|
EQUIVALENTS.put(style1.horizontal, style2.horizontal);
|
||||||
|
EQUIVALENTS.put(style1.vertical, style2.vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerMixedWithAirCombinations(char vertical, char horizontal) {
|
||||||
|
registerCorner(vertical, vertical, ' ', NONE, vertical);
|
||||||
|
registerCorner(vertical, vertical, NONE, ' ', vertical);
|
||||||
|
registerCorner(vertical, vertical, ' ', ' ', vertical);
|
||||||
|
registerCorner(' ', NONE, horizontal, horizontal, horizontal);
|
||||||
|
registerCorner(NONE, ' ', horizontal, horizontal, horizontal);
|
||||||
|
registerCorner(' ', ' ', horizontal, horizontal, horizontal);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register corner glyphs for a given set, not taking care of mixed style intersections.
|
||||||
|
*/
|
||||||
|
private static void registerCorners(String list) {
|
||||||
|
char horizontal = list.charAt(0);
|
||||||
|
char vertical = list.charAt(1);
|
||||||
|
registerCorner(NONE, vertical, NONE, horizontal, list.charAt(2));
|
||||||
|
registerCorner(NONE, vertical, horizontal, NONE, list.charAt(3));
|
||||||
|
registerCorner(vertical, NONE, NONE, horizontal, list.charAt(4));
|
||||||
|
registerCorner(vertical, NONE, horizontal, NONE, list.charAt(5));
|
||||||
|
registerCorner(vertical, vertical, NONE, horizontal, list.charAt(6));
|
||||||
|
registerCorner(vertical, vertical, horizontal, NONE, list.charAt(7));
|
||||||
|
registerCorner(NONE, vertical, horizontal, horizontal, list.charAt(8));
|
||||||
|
registerCorner(vertical, NONE, horizontal, horizontal, list.charAt(9));
|
||||||
|
registerCorner(vertical, vertical, horizontal, horizontal, list.charAt(10));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerCorner(char above, char below, char left, char right, char corner) {
|
||||||
|
long key = key(above, below, left, right);
|
||||||
|
CORNERS.put(key, corner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static char intersection(char above, char below, char left, char right) {
|
||||||
|
above = EQUIVALENTS.get(above) != null ? EQUIVALENTS.get(above) : above;
|
||||||
|
below = EQUIVALENTS.get(below) != null ? EQUIVALENTS.get(below) : below;
|
||||||
|
left = EQUIVALENTS.get(left) != null ? EQUIVALENTS.get(left) : left;
|
||||||
|
right = EQUIVALENTS.get(right) != null ? EQUIVALENTS.get(right) : right;
|
||||||
|
Character character = CORNERS.get(key(above, below, left, right));
|
||||||
|
return character != null ? character : NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long key(char above, char below, char left, char right) {
|
||||||
|
return (long) above << 48 | (long) below << 32 | (long) left << 16 | (long) right;
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderStyle(char vertical, char horizontal) {
|
||||||
|
this.vertical = vertical;
|
||||||
|
this.horizontal = horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to specify where some components of a Table may be applied.
|
||||||
|
*
|
||||||
|
* <p>Some commonly used matchers can be created <i>via</i> {@link CellMatchers}.</p>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface CellMatcher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether a given cell of the table should match.
|
||||||
|
* @param row the row being tested.
|
||||||
|
* @param column the column being tested
|
||||||
|
* @param model the data model of the table
|
||||||
|
*/
|
||||||
|
public boolean matches(int row, int column, TableModel model);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains factory methods for commonly used {@link CellMatcher}s.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class CellMatchers {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a matcher that applies to every cell of the table.
|
||||||
|
*/
|
||||||
|
public static CellMatcher table() {
|
||||||
|
return new CellMatcher() {
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a matcher that applies to every cell of some column of the table.
|
||||||
|
* @param col the column to select
|
||||||
|
*/
|
||||||
|
public static CellMatcher column(final int col) {
|
||||||
|
return new CellMatcher() {
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
return col == column;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a matcher that applies to every cell of some row of the table.
|
||||||
|
* @param theRow the row to select
|
||||||
|
*/
|
||||||
|
public static CellMatcher row(final int theRow) {
|
||||||
|
return new CellMatcher() {
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
return theRow == row;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param theRow the row to select
|
||||||
|
* @param col the column to select
|
||||||
|
* @return a matcher that applies to exactly one cell of the table, identified by its row and colum.
|
||||||
|
*/
|
||||||
|
public static CellMatcher at(final int theRow, final int col) {
|
||||||
|
return new CellMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
return row == theRow && column == col;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param clazz the type that cells should contain
|
||||||
|
* @return a matcher that matches cells whose content is of a certain type
|
||||||
|
*/
|
||||||
|
public static CellMatcher ofType(final Class<?> clazz) {
|
||||||
|
return new CellMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(int row, int column, TableModel model) {
|
||||||
|
Object value = model.getValue(row, column);
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return clazz.isAssignableFrom(value.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decorator Aligner that checks the Aligner invariants contract, useful for debugging.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class DebugAligner implements Aligner {
|
||||||
|
|
||||||
|
private final Aligner delegate;
|
||||||
|
|
||||||
|
public DebugAligner(Aligner delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||||
|
String[] result = delegate.align(text, cellWidth, cellHeight);
|
||||||
|
Assert.isTrue(result.length == cellHeight, String.format("%s had the wrong number of lines (%d), expected %d",
|
||||||
|
Arrays.asList(result), result.length, cellHeight));
|
||||||
|
for (String s : result) {
|
||||||
|
Assert.isTrue(s.length() == cellWidth, String.format("'%s' had wrong length (%d), expected %d", s, s.length(),
|
||||||
|
cellWidth));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TextWrapper that delegates to another but makes sure that the contract is not violated.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class DebugTextWrapper implements TextWrapper {
|
||||||
|
|
||||||
|
private final TextWrapper delegate;
|
||||||
|
|
||||||
|
public DebugTextWrapper(TextWrapper delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] wrap(String[] original, int columnWidth) {
|
||||||
|
String[] result = delegate.wrap(original, columnWidth);
|
||||||
|
for (String s : result) {
|
||||||
|
Assert.isTrue(s.length() == columnWidth, String.format("'%s' has the wrong length (%d), expected %d", s, s.length(), columnWidth));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very simple formatter that uses {@link Object#toString()} and splits on newlines.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class DefaultFormatter implements Formatter {
|
||||||
|
|
||||||
|
public String[] format(Object value) {
|
||||||
|
return value == null ? new String[] {""} : value.toString().split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Text wrapper that wraps at "word" boundaries. The default delimiter is the space character.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class DelimiterTextWrapper implements TextWrapper {
|
||||||
|
|
||||||
|
private final char delimiter;
|
||||||
|
|
||||||
|
public DelimiterTextWrapper() {
|
||||||
|
this(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public DelimiterTextWrapper(char delimiter) {
|
||||||
|
this.delimiter = delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] wrap(String[] original, int columnWidth) {
|
||||||
|
List<String> result = new ArrayList<String>(original.length);
|
||||||
|
for (String line : original) {
|
||||||
|
while (line.length() > columnWidth) {
|
||||||
|
int split = line.lastIndexOf(delimiter, columnWidth);
|
||||||
|
String toAdd = split == -1 ? line.substring(0, columnWidth) : line.substring(0, split);
|
||||||
|
result.add(String.format("%-" + columnWidth + "s", toAdd));
|
||||||
|
line = line.substring(split == -1 ? columnWidth : split + 1);
|
||||||
|
}
|
||||||
|
if (columnWidth > 0) {
|
||||||
|
result.add(String.format("%-" + columnWidth + "s", line)); // right pad if necessary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(new String[result.size()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Formatter is responsible for the initial rendering of a value to lines of text.
|
||||||
|
*
|
||||||
|
* <p>Note that this representation is likely to be altered later in the pipeline, for the
|
||||||
|
* purpose of text wrapping and aligning. The role of a formatter is merely to give the
|
||||||
|
* raw text representation (<i>e.g.</i> format numbers).</p>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface Formatter {
|
||||||
|
|
||||||
|
public String[] format(Object value);
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A text alignment strategy that aligns text horizontally so that all instances of some special character(s)
|
||||||
|
* line up perfectly in a column.
|
||||||
|
*
|
||||||
|
* <p>Typically used to render numbers which may or may not have a decimal point, or series of key-value pairs</p>
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class KeyValueHorizontalAligner implements Aligner {
|
||||||
|
|
||||||
|
private final String delimiter;
|
||||||
|
|
||||||
|
public KeyValueHorizontalAligner(String delimiter) {
|
||||||
|
this.delimiter = delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||||
|
|
||||||
|
String[] result = new String[cellHeight];
|
||||||
|
int alignOffset = 0;
|
||||||
|
for (String line : text) {
|
||||||
|
alignOffset = Math.max(alignOffset, line.trim().indexOf(delimiter));
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (String line : text) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
int offset = trimmed.indexOf(delimiter);
|
||||||
|
if (offset >= 0) {
|
||||||
|
// It is possible that aligning would trigger overflow
|
||||||
|
// Make sure not to
|
||||||
|
int offsetToUse = Math.min(alignOffset - offset, cellWidth - trimmed.length());
|
||||||
|
result[i++] = pad(offsetToUse, cellWidth - trimmed.length() - offsetToUse, trimmed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[i++] = pad(0, cellWidth - line.length(), line);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String pad(int left, int right, String original) {
|
||||||
|
StringBuilder sb = new StringBuilder(left + original.length() + right);
|
||||||
|
for (int i = 0; i < left; i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(original);
|
||||||
|
for (int i = 0; i < right; i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SizeConstraints implementation that is tailored to rendering a series
|
||||||
|
* of {@literal key = value} pairs. Computes extents so that equal signs (or any other
|
||||||
|
* configurable delimiter) line up vertically.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class KeyValueSizeConstraints implements SizeConstraints {
|
||||||
|
|
||||||
|
private final String delimiter;
|
||||||
|
|
||||||
|
public KeyValueSizeConstraints(String delimiter) {
|
||||||
|
this.delimiter = delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String leftTrim(String raw) {
|
||||||
|
int start = 0;
|
||||||
|
int length = raw.length();
|
||||||
|
while (start < length && raw.charAt(start) == ' ') {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
return raw.substring(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String rightTrim(String raw) {
|
||||||
|
int end = raw.length();
|
||||||
|
while (end > 0 && raw.charAt(end - 1) == ' ') {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return raw.substring(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||||
|
|
||||||
|
// We need to make sure we take care of the case where we have
|
||||||
|
// k = long-value
|
||||||
|
// long-key = v
|
||||||
|
// as the real maximal extent is size(long-key) + size( = ) + size(long-value)
|
||||||
|
|
||||||
|
// The minimal extent in the example above is size(long-value)
|
||||||
|
|
||||||
|
int maxLeft = 0;
|
||||||
|
int maxRight = 0;
|
||||||
|
int min = 0;
|
||||||
|
for (String line : raw) {
|
||||||
|
String lineToConsider = line.trim();
|
||||||
|
int offset = lineToConsider.indexOf(delimiter);
|
||||||
|
|
||||||
|
if (offset != -1) {
|
||||||
|
// Compute minimal case (line can be split, decide where to put the delimiter)
|
||||||
|
String minimalLeftPart = lineToConsider.substring(0, offset).trim();
|
||||||
|
String minimalRightPart = lineToConsider.substring(offset + delimiter.length()).trim();
|
||||||
|
int left = minimalLeftPart.length();
|
||||||
|
int right = minimalRightPart.length();
|
||||||
|
int case1 = Math.max(left, right + leftTrim(delimiter).length());
|
||||||
|
int case2 = Math.max(left + rightTrim(delimiter).length(), right);
|
||||||
|
int bestMin = Math.min(case1, case2);
|
||||||
|
min = Math.max(min, bestMin);
|
||||||
|
|
||||||
|
// Compute maximal case (sum of worst case on left and right)
|
||||||
|
maxLeft = Math.max(maxLeft, offset);
|
||||||
|
int after = lineToConsider.length() - offset - delimiter.length();
|
||||||
|
maxRight = Math.max(maxRight, after);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
min = Math.max(min, lineToConsider.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Extent(min, maxLeft + delimiter.length() + maxRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TextWrapper implementation tailored for key-value rendering (working in concert
|
||||||
|
* with {@link KeyValueSizeConstraints}, {@link KeyValueHorizontalAligner}), that tries its
|
||||||
|
* best to vertically align some delimiter character (default '=').
|
||||||
|
*/
|
||||||
|
public class KeyValueTextWrapper implements TextWrapper {
|
||||||
|
|
||||||
|
private final String delimiter;
|
||||||
|
|
||||||
|
public KeyValueTextWrapper() {
|
||||||
|
this("=");
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValueTextWrapper(String delimiter) {
|
||||||
|
this.delimiter = delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] wrap(String[] original, int columnWidth) {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
for (String line : original) {
|
||||||
|
line = line.trim();
|
||||||
|
while (line.length() > columnWidth) {
|
||||||
|
int cut = line.lastIndexOf(delimiter, columnWidth);
|
||||||
|
if (cut == -1) {
|
||||||
|
cut = columnWidth;
|
||||||
|
}
|
||||||
|
else if (cut + delimiter.length() <= columnWidth) {
|
||||||
|
cut = cut + delimiter.length();
|
||||||
|
}
|
||||||
|
result.add(rightPad(line.substring(0, cut), columnWidth));
|
||||||
|
line = line.substring(cut).trim();
|
||||||
|
}
|
||||||
|
if (line.length() > 0) {
|
||||||
|
result.add(rightPad(line, columnWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(new String[result.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String rightPad(String raw, int width) {
|
||||||
|
StringBuilder result = new StringBuilder(raw);
|
||||||
|
for (int i = raw.length(); i < width; i++) {
|
||||||
|
result.append(' ');
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A formatter suited for key-value pairs, that renders each mapping on a new line.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class MapFormatter implements Formatter {
|
||||||
|
|
||||||
|
private final String separator;
|
||||||
|
|
||||||
|
public MapFormatter(String separator) {
|
||||||
|
this.separator = separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] format(Object value) {
|
||||||
|
Map<?, ?> map = (Map<?, ?>) value;
|
||||||
|
String[] result = new String[map.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<?, ?> kv : map.entrySet()) {
|
||||||
|
result[i++] = kv.getKey() + separator + kv.getValue();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sizing strategy that will impose the longest line width on cells.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class NoWrapSizeConstraints implements SizeConstraints {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||||
|
int max = 0;
|
||||||
|
for (String line : raw) {
|
||||||
|
max = Math.max(max, line.length());
|
||||||
|
}
|
||||||
|
return new Extent(max, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An horizontal alignment strategy that allows alignment to the left, center or right.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public enum SimpleHorizontalAligner implements Aligner {
|
||||||
|
|
||||||
|
left, center, right;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||||
|
String[] result = new String[cellHeight];
|
||||||
|
for (int i = 0; i < cellHeight; i++) {
|
||||||
|
String line = (i < text.length && text[i] != null) ? text[i].trim() : "";
|
||||||
|
|
||||||
|
int paddingToDistribute = cellWidth - line.length();
|
||||||
|
|
||||||
|
int padLeft;
|
||||||
|
int padRight;
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case center: {
|
||||||
|
int carry = paddingToDistribute % 2;
|
||||||
|
paddingToDistribute = paddingToDistribute - carry;
|
||||||
|
padLeft = padRight = paddingToDistribute / 2;
|
||||||
|
padRight += carry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case right: {
|
||||||
|
padLeft = paddingToDistribute;
|
||||||
|
padRight = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case left: {
|
||||||
|
padLeft = 0;
|
||||||
|
padRight = paddingToDistribute;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder(cellWidth);
|
||||||
|
for (int j = 0; j < padLeft; j++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(line);
|
||||||
|
for (int j = 0; j < padRight; j++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i] = sb.toString();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alignment strategy that allows simple vertical alignment to top, middle or bottom.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public enum SimpleVerticalAligner implements Aligner {
|
||||||
|
|
||||||
|
top, middle, bottom;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||||
|
String[] result = new String[cellHeight];
|
||||||
|
int blanksBefore = 0;
|
||||||
|
int blanksAfter = 0;
|
||||||
|
boolean atLeastOneNonEmptyRow = false;
|
||||||
|
for (int row = 0; row < text.length; row++) {
|
||||||
|
if (text[row] == null || text[row].trim().equals("")) {
|
||||||
|
blanksBefore++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
atLeastOneNonEmptyRow = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In case of full blank, don't count blank rows twice
|
||||||
|
if (atLeastOneNonEmptyRow) {
|
||||||
|
for (int row = text.length - 1; row >= 0; row--) {
|
||||||
|
if (text[row] == null || text[row].trim().equals("")) {
|
||||||
|
blanksAfter++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String filler = spaces(cellWidth);
|
||||||
|
|
||||||
|
int padBefore;
|
||||||
|
int padAfter;
|
||||||
|
int nonBlankLines = text.length - blanksAfter - blanksBefore;
|
||||||
|
int paddingToDistribute = cellHeight - nonBlankLines;
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case middle: {
|
||||||
|
int carry = paddingToDistribute % 2;
|
||||||
|
paddingToDistribute = paddingToDistribute - carry;
|
||||||
|
padBefore = padAfter = paddingToDistribute / 2;
|
||||||
|
padAfter += carry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case bottom: {
|
||||||
|
padBefore = paddingToDistribute;
|
||||||
|
padAfter = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case top: {
|
||||||
|
padBefore = 0;
|
||||||
|
padAfter = paddingToDistribute;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.fill(result, 0, padBefore, filler);
|
||||||
|
System.arraycopy(text, blanksBefore, result, padBefore, nonBlankLines);
|
||||||
|
Arrays.fill(result, result.length - padAfter, result.length, filler);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String spaces(int width) {
|
||||||
|
char[] data = new char[width];
|
||||||
|
Arrays.fill(data, ' ');
|
||||||
|
return new String(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy for computing the dimensions of a table cell.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface SizeConstraints {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the minimum and maximum width of the cell, given its raw content.
|
||||||
|
* @param raw the raw String representation of the cell contents (may be reformatted later, eg wrapped)
|
||||||
|
* @param tableWidth the whole available width for the table
|
||||||
|
* @param nbColumns the number of columns in the table
|
||||||
|
*/
|
||||||
|
Extent width(String[] raw, int tableWidth, int nbColumns);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds both a minimum and maximum width.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
class Extent {
|
||||||
|
|
||||||
|
public final int min;
|
||||||
|
|
||||||
|
public final int max;
|
||||||
|
|
||||||
|
public Extent(int min, int max) {
|
||||||
|
Assert.isTrue(min <= max, "min must be less than max");
|
||||||
|
Assert.isTrue(0 <= min, "min and max must be positive");
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import static org.springframework.shell.table.BorderSpecification.NONE;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.shell.TerminalSizeAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the central API for table rendering. A Table object is constructed with a given
|
||||||
|
* TableModel, which holds raw table contents. Its rendering logic is then altered by applying
|
||||||
|
* various customizations, in a fashion very similar to what is used <i>e.g.</i> in a spreadsheet
|
||||||
|
* program:<ol>
|
||||||
|
* <li>{@link #formatters formatters} know how to derive character data out of raw data. For
|
||||||
|
* example, numbers are
|
||||||
|
* formatted according to a Locale, or Maps are emitted as a series of {@literal key=value} lines</li>
|
||||||
|
* <li>{@link #sizeConstraints size constraints} are then applied, which decide how
|
||||||
|
* much column real estate to allocate to cells</li>
|
||||||
|
* <li>{@link #wrappers text wrapping policies} are applied once the column sizes
|
||||||
|
* are known</li>
|
||||||
|
* <li>finally, {@link #aligners alignment} strategies actually render
|
||||||
|
* text as a series of space-padded strings that draw nicely on screen.</li>
|
||||||
|
* </ol>
|
||||||
|
* All those customizations are applied selectively on the Table cells thanks to a {@link CellMatcher}: One can
|
||||||
|
* decide to right pad column number 3, or to format in a certain way all instances of {@literal java.util.Map}.
|
||||||
|
*
|
||||||
|
* <p>Of course, all of those customizations often work hand in hand, and not all combinations make sense:
|
||||||
|
* one needs to anticipate the fact that text will be split using the ' ' (space) character to properly
|
||||||
|
* calculate column sizes.</p>
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class Table implements TerminalSizeAware {
|
||||||
|
|
||||||
|
private final int rows;
|
||||||
|
|
||||||
|
private final int columns;
|
||||||
|
|
||||||
|
private TableModel model;
|
||||||
|
|
||||||
|
private Map<CellMatcher, Formatter> formatters = new LinkedHashMap<CellMatcher, Formatter>();
|
||||||
|
|
||||||
|
private Map<CellMatcher, SizeConstraints> sizeConstraints = new LinkedHashMap<CellMatcher, SizeConstraints>();
|
||||||
|
|
||||||
|
private Map<CellMatcher, TextWrapper> wrappers = new LinkedHashMap<CellMatcher, TextWrapper>();
|
||||||
|
|
||||||
|
private Map<CellMatcher, Aligner> aligners = new LinkedHashMap<CellMatcher, Aligner>();
|
||||||
|
|
||||||
|
private List<BorderSpecification> borderSpecifications = new ArrayList<BorderSpecification>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new Table with the given model and customizers.
|
||||||
|
* The passed in LinkedHashMap should be in reverse-insertion order (<i>i.e.</i> the first CellMatcher
|
||||||
|
* found in iteration order will "win").
|
||||||
|
*
|
||||||
|
* @see TableBuilder#build()
|
||||||
|
*/
|
||||||
|
/*package*/ Table(TableModel model,
|
||||||
|
LinkedHashMap<CellMatcher, Formatter> formatters,
|
||||||
|
LinkedHashMap<CellMatcher, SizeConstraints> sizeConstraints,
|
||||||
|
LinkedHashMap<CellMatcher, TextWrapper> wrappers,
|
||||||
|
LinkedHashMap<CellMatcher, Aligner> aligners,
|
||||||
|
List<BorderSpecification> borderSpecifications) {
|
||||||
|
this.model = model;
|
||||||
|
this.formatters = formatters;
|
||||||
|
this.sizeConstraints = sizeConstraints;
|
||||||
|
this.wrappers = wrappers;
|
||||||
|
this.aligners = aligners;
|
||||||
|
this.borderSpecifications = borderSpecifications;
|
||||||
|
rows = model.getRowCount();
|
||||||
|
columns = model.getColumnCount();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableModel getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String render(int totalAvailableWidth) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
int[] cellHeights = new int[rows];
|
||||||
|
int[] cellWidths;
|
||||||
|
int[] minCellWidths = new int[columns];
|
||||||
|
int[] maxCellWidths = new int[columns];
|
||||||
|
|
||||||
|
String[][][] subLines = new String[rows][columns][];
|
||||||
|
|
||||||
|
Borders borders = new Borders();
|
||||||
|
int widthAvailableForContents = totalAvailableWidth - borders.getNumberOfVerticalBorders();
|
||||||
|
|
||||||
|
// First, compute desired column widths
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
Object value = model.getValue(row, column);
|
||||||
|
String[] lines = getFormatter(row, column).format(value);
|
||||||
|
subLines[row][column] = lines;
|
||||||
|
|
||||||
|
SizeConstraints.Extent extent = getSizeConstraints(row, column).width(lines, widthAvailableForContents, columns);
|
||||||
|
|
||||||
|
minCellWidths[column] = Math.max(minCellWidths[column], extent.min);
|
||||||
|
maxCellWidths[column] = Math.max(maxCellWidths[column], extent.max);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cellWidths = computeColumnWidths(widthAvailableForContents, minCellWidths, maxCellWidths);
|
||||||
|
// Now that widths are known, apply wrapping & render
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
subLines[row][column] = getWrapper(row, column).wrap(subLines[row][column], cellWidths[column]);
|
||||||
|
cellHeights[row] = Math.max(cellHeights[row], subLines[row][column].length);
|
||||||
|
}
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
for (Map.Entry<CellMatcher, Aligner> kv : aligners.entrySet()) {
|
||||||
|
if (kv.getKey().matches(row, column, model)) {
|
||||||
|
subLines[row][column] = kv.getValue().align(subLines[row][column], cellWidths[column], cellHeights[row]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
|
||||||
|
// TOP CELL BORDER
|
||||||
|
int before = result.length();
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
borders.paintCorner(row, column, result);
|
||||||
|
borders.paintHorizontal(row, column, cellWidths[column], result);
|
||||||
|
}
|
||||||
|
borders.paintCorner(row, columns, result);
|
||||||
|
if (result.length() > before) {
|
||||||
|
result.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int subRow = 0; subRow < cellHeights[row]; subRow++) {
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
// LEFT CELL BORDER
|
||||||
|
borders.paintVertical(row, column, result);
|
||||||
|
String[] lines = subLines[row][column];
|
||||||
|
result.append(lines[subRow]);
|
||||||
|
}
|
||||||
|
// TABLE RIGHT BORDER
|
||||||
|
borders.paintVertical(row, columns, result);
|
||||||
|
result.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLE BOTTOM BORDER
|
||||||
|
int before = result.length();
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
borders.paintCorner(rows, column, result);
|
||||||
|
borders.paintHorizontal(rows, column, cellWidths[column], result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLE BOTTOM RIGHT CORNER
|
||||||
|
borders.paintCorner(rows, columns, result);
|
||||||
|
if (result.length() > before) {
|
||||||
|
result.append('\n');
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] computeColumnWidths(int availableWidth, int[] minCellWidths, int[] maxCellWidths) {
|
||||||
|
|
||||||
|
int[] cellWidths;
|
||||||
|
int minTableWidth = 0, maxTableWidth = 0;
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
minTableWidth += minCellWidths[column];
|
||||||
|
maxTableWidth += maxCellWidths[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can use max desired width
|
||||||
|
if (maxTableWidth <= availableWidth) {
|
||||||
|
cellWidths = maxCellWidths;
|
||||||
|
} // will overflow
|
||||||
|
else if (minTableWidth >= availableWidth) {
|
||||||
|
cellWidths = minCellWidths;
|
||||||
|
} // Redistribute nicely
|
||||||
|
else {
|
||||||
|
int W = availableWidth - minTableWidth;
|
||||||
|
int D = maxTableWidth - minTableWidth;
|
||||||
|
cellWidths = new int[columns];
|
||||||
|
for (int column = 0; column < columns; column++) {
|
||||||
|
cellWidths[column] = minCellWidths[column] + W * (maxCellWidths[column] - minCellWidths[column]) / D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellWidths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextWrapper getWrapper(int row, int column) {
|
||||||
|
for (Map.Entry<CellMatcher, TextWrapper> kv : wrappers.entrySet()) {
|
||||||
|
if (kv.getKey().matches(row, column, model)) {
|
||||||
|
return kv.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SizeConstraints getSizeConstraints(int row, int column) {
|
||||||
|
for (Map.Entry<CellMatcher, SizeConstraints> kv : sizeConstraints.entrySet()) {
|
||||||
|
if (kv.getKey().matches(row, column, model)) {
|
||||||
|
return kv.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Formatter getFormatter(int row, int column) {
|
||||||
|
for (Map.Entry<CellMatcher, Formatter> kv : formatters.entrySet()) {
|
||||||
|
if (kv.getKey().matches(row, column, model)) {
|
||||||
|
return kv.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of this class knows where to paint border glyphs.
|
||||||
|
*
|
||||||
|
* <p>In all instance arrays, 'row' and 'column' are actually indices in-between
|
||||||
|
* table rows and columns. Hence, sizes are larger by one.</p>
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
private class Borders {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glyph to paint a vertical line at row,col.
|
||||||
|
*/
|
||||||
|
private char[][] verticals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glyph to paint a horizontal line at row,col.
|
||||||
|
*/
|
||||||
|
private char[][] horizontals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of corner, if any, to paint at row,col.
|
||||||
|
*/
|
||||||
|
private char[][] corners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if at least one vertical bar exists in that col.
|
||||||
|
*/
|
||||||
|
private boolean[] vFillers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if at least one horizontal bar exists in that row.
|
||||||
|
*/
|
||||||
|
private boolean[] hFillers;
|
||||||
|
|
||||||
|
public Borders() {
|
||||||
|
verticals = new char[rows][columns + 1];
|
||||||
|
horizontals = new char[rows + 1][columns];
|
||||||
|
corners = new char[rows + 1][columns + 1];
|
||||||
|
vFillers = new boolean[columns + 1];
|
||||||
|
hFillers = new boolean[rows + 1];
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
|
||||||
|
for (int row = 0; row <= rows; row++) {
|
||||||
|
for (int column = 0; column <= columns; column++) {
|
||||||
|
for (BorderSpecification bs : borderSpecifications) {
|
||||||
|
if (row < rows) {
|
||||||
|
char verticalThere = bs.verticals(row, column);
|
||||||
|
if (verticalThere != BorderStyle.NONE) {
|
||||||
|
this.verticals[row][column] = verticalThere;
|
||||||
|
vFillers[column] |= true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (column < columns) {
|
||||||
|
char horizontalThere = bs.horizontals(row, column);
|
||||||
|
if (horizontalThere != BorderStyle.NONE) {
|
||||||
|
this.horizontals[row][column] = horizontalThere;
|
||||||
|
hFillers[row] |= true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute corners when horizontals & verticals intersect
|
||||||
|
for (int row = 0; row <= rows; row++) {
|
||||||
|
for (int column = 0; column <= columns; column++) {
|
||||||
|
char left = (column - 1 >= 0) ? horizontals[row][column - 1] : NONE;
|
||||||
|
char right = (column < columns) ? horizontals[row][column] : NONE;
|
||||||
|
char above = (row - 1 >= 0) ? verticals[row - 1][column] : NONE;
|
||||||
|
char below = (row < rows) ? verticals[row][column] : NONE;
|
||||||
|
corners[row][column] = BorderStyle.intersection(above, below, left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintCorner(int row, int column, StringBuilder stringBuilder) {
|
||||||
|
if (corners[row][column] != NONE) {
|
||||||
|
stringBuilder.append(corners[row][column]);
|
||||||
|
} // If there is a border in same row|column, paint filler
|
||||||
|
else if (vFillers[column] && hFillers[row]) {
|
||||||
|
stringBuilder.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintVertical(int row, int column, StringBuilder stringBuilder) {
|
||||||
|
if (verticals[row][column] != NONE) {
|
||||||
|
stringBuilder.append(verticals[row][column]);
|
||||||
|
}
|
||||||
|
else if (vFillers[column]) {
|
||||||
|
stringBuilder.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintHorizontal(int row, int column, int width, StringBuilder stringBuilder) {
|
||||||
|
if (horizontals[row][column] != NONE) {
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
stringBuilder.append(horizontals[row][column]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (hFillers[row]) {
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
stringBuilder.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of vertical borders, and hence the space consumed by those.
|
||||||
|
*/
|
||||||
|
public int getNumberOfVerticalBorders() {
|
||||||
|
int result = 0;
|
||||||
|
for (boolean b : vFillers) {
|
||||||
|
if (b) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import static org.springframework.shell.table.BorderSpecification.FULL;
|
||||||
|
import static org.springframework.shell.table.BorderSpecification.INNER;
|
||||||
|
import static org.springframework.shell.table.BorderSpecification.INNER_VERTICAL;
|
||||||
|
import static org.springframework.shell.table.BorderSpecification.OUTLINE;
|
||||||
|
import static org.springframework.shell.table.SimpleHorizontalAligner.left;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder class to incrementally configure a Table.
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class TableBuilder {
|
||||||
|
|
||||||
|
private final TableModel model;
|
||||||
|
|
||||||
|
private final Map<CellMatcher, Formatter> formatters = new LinkedHashMap<CellMatcher, Formatter>();
|
||||||
|
|
||||||
|
private final Map<CellMatcher, SizeConstraints> sizeConstraints = new LinkedHashMap<CellMatcher, SizeConstraints>();
|
||||||
|
|
||||||
|
private final Map<CellMatcher, TextWrapper> wrappers = new LinkedHashMap<CellMatcher, TextWrapper>();
|
||||||
|
|
||||||
|
private final LinkedHashMap<CellMatcher, Aligner> aligners = new LinkedHashMap<CellMatcher, Aligner>();
|
||||||
|
|
||||||
|
private final List<BorderSpecification> borderSpecifications = new ArrayList<BorderSpecification>();
|
||||||
|
|
||||||
|
private final int rows;
|
||||||
|
|
||||||
|
private final int columns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a table with the given model. The table will use the following
|
||||||
|
* strategies for all cells, unless overridden:<ul>
|
||||||
|
* <li>{@link DefaultFormatter default formatting} using {@literal toString()}</li>
|
||||||
|
* <li>{@link AutoSizeConstraints sizing strategy} trying to use the maximum space, resorting to splitting lines on
|
||||||
|
* spaces</li>
|
||||||
|
* <li>{@link DelimiterTextWrapper wrapping text} on space characters</li>
|
||||||
|
* <li>{@link SimpleHorizontalAligner left aligning} text.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param model the data model of the table to construct
|
||||||
|
*/
|
||||||
|
|
||||||
|
public TableBuilder(TableModel model) {
|
||||||
|
this.model = model;
|
||||||
|
rows = model.getRowCount();
|
||||||
|
columns = model.getColumnCount();
|
||||||
|
|
||||||
|
formatters.put(CellMatchers.table(), new DefaultFormatter());
|
||||||
|
sizeConstraints.put(CellMatchers.table(), new AutoSizeConstraints());
|
||||||
|
wrappers.put(CellMatchers.table(), new DelimiterTextWrapper());
|
||||||
|
aligners.put(CellMatchers.table(), left);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableBuilder addBorder(int top, int left, int bottom, int right, int match, BorderStyle style) {
|
||||||
|
Assert.isTrue(top >= 0 && top < rows, "top row must be positive and less than total number of rows");
|
||||||
|
Assert.isTrue(left >= 0 && left < columns, "left column must be positive and less than total number of columns");
|
||||||
|
Assert.isTrue(bottom > top && bottom <= rows, "bottom row must be greater than top and less than total number of rows");
|
||||||
|
Assert.isTrue(right >= left && right <= columns, "right column must be greater than left and less than total number of columns");
|
||||||
|
Assert.notNull(style, "style cannot be null");
|
||||||
|
borderSpecifications.add(new BorderSpecification(top, left, bottom, right, match, style));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableModel getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub on(CellMatcher matcher) {
|
||||||
|
return new CellMatcherStub(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Table build() {
|
||||||
|
return new Table(model,
|
||||||
|
reverse(formatters),
|
||||||
|
reverse(sizeConstraints),
|
||||||
|
reverse(wrappers),
|
||||||
|
aligners,
|
||||||
|
borderSpecifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BorderStub paintBorder(BorderStyle style, int match) {
|
||||||
|
return new BorderStub(style, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience methods for borders
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a border on the outline of the whole table.
|
||||||
|
* @param style the style to apply
|
||||||
|
* @return this, for method chaining
|
||||||
|
*/
|
||||||
|
public TableBuilder addOutlineBorder(BorderStyle style) {
|
||||||
|
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), OUTLINE, style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a border on the outline of the whole table, as well as around the first row.
|
||||||
|
* @param style the style to apply
|
||||||
|
* @return this, for method chaining
|
||||||
|
*/
|
||||||
|
public TableBuilder addHeaderBorder(BorderStyle style) {
|
||||||
|
this.addBorder(0, 0, 1, model.getColumnCount(), OUTLINE, style);
|
||||||
|
return addOutlineBorder(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a border around each and every cell of the table.
|
||||||
|
*
|
||||||
|
* @param style the style to apply
|
||||||
|
* @return this, for method chaining
|
||||||
|
*/
|
||||||
|
public TableBuilder addFullBorder(BorderStyle style) {
|
||||||
|
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), FULL, style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a border on the outline of the whole table, around the first row and draw vertical lines
|
||||||
|
* around each column.
|
||||||
|
*
|
||||||
|
* @param style the style to apply
|
||||||
|
* @return this, for method chaining
|
||||||
|
*/
|
||||||
|
public TableBuilder addHeaderAndVerticalsBorders(BorderStyle style) {
|
||||||
|
this.addBorder(0, 0, 1, model.getColumnCount(), OUTLINE, style);
|
||||||
|
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), OUTLINE | INNER_VERTICAL, style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a border on the inner verticals and horizontals of the table, but not on the outline.
|
||||||
|
*
|
||||||
|
* @param style the style to apply
|
||||||
|
* @return this, for method chaining
|
||||||
|
*/
|
||||||
|
public TableBuilder addInnerBorder(BorderStyle style) {
|
||||||
|
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), INNER, style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <K, V> LinkedHashMap<K, V> reverse(Map<K, V> original) {
|
||||||
|
LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(original.size());
|
||||||
|
List<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, V>>(original.entrySet());
|
||||||
|
for (int i = entries.size() - 1; i >= 0; i--) {
|
||||||
|
Map.Entry<K, V> entry = entries.get(i);
|
||||||
|
result.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CellMatcherStub {
|
||||||
|
|
||||||
|
private final CellMatcher cellMatcher;
|
||||||
|
|
||||||
|
private CellMatcherStub(CellMatcher cellMatcher) {
|
||||||
|
this.cellMatcher = cellMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub addFormatter(Formatter formatter) {
|
||||||
|
formatters.put(this.cellMatcher, formatter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub addSizer(SizeConstraints sizer) {
|
||||||
|
sizeConstraints.put(this.cellMatcher, sizer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub addWrapper(TextWrapper textWrapper) {
|
||||||
|
wrappers.put(this.cellMatcher, textWrapper);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub addAligner(Aligner aligner) {
|
||||||
|
aligners.put(this.cellMatcher, aligner);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellMatcherStub on(CellMatcher other) {
|
||||||
|
return TableBuilder.this.on(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableBuilder and() {
|
||||||
|
return TableBuilder.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Table build() {
|
||||||
|
return TableBuilder.this.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BorderStub {
|
||||||
|
|
||||||
|
private final BorderStyle style;
|
||||||
|
|
||||||
|
private final int match;
|
||||||
|
|
||||||
|
private BorderStub(BorderStyle style, int match) {
|
||||||
|
this.style = style;
|
||||||
|
this.match = match;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TopLeft fromRowColumn(int row, int column) {
|
||||||
|
return new TopLeft(row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TopLeft fromTopLeft() {
|
||||||
|
return new TopLeft(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TopLeft {
|
||||||
|
private final int row;
|
||||||
|
|
||||||
|
private final int column;
|
||||||
|
|
||||||
|
private TopLeft(int row, int column) {
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableBuilder toRowColumn(int row, int column) {
|
||||||
|
TableBuilder.this.addBorder(TopLeft.this.row, TopLeft.this.column, row, column, BorderStub.this.match, BorderStub.this.style);
|
||||||
|
return TableBuilder.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableBuilder toBottomRight() {
|
||||||
|
return toRowColumn(model.getRowCount(), model.getColumnCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstracts away the contract a {@link Table} will use to retrieve tabular data.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public abstract class TableModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of rows that can be queried.
|
||||||
|
* Values between 0 and {@code rowCount-1} inclusive are valid values.
|
||||||
|
*/
|
||||||
|
public abstract int getRowCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of columns that can be queried.
|
||||||
|
* Values between 0 and {@code columnCount-1} inclusive are valid values.
|
||||||
|
*/
|
||||||
|
public abstract int getColumnCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the data value to be displayed at a given row and column, which may be null.
|
||||||
|
* @param row the row that is being queried
|
||||||
|
* @param column the column that is being queried
|
||||||
|
*/
|
||||||
|
public abstract Object getValue(int row, int column);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a transposed view of this model, where rows become columns and vice-versa.
|
||||||
|
*/
|
||||||
|
public TableModel transpose() {
|
||||||
|
return new TableModel() {
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return TableModel.this.getColumnCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return TableModel.this.getRowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue(int row, int column) {
|
||||||
|
return TableModel.this.getValue(column, row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to build a TableModel incrementally.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class TableModelBuilder<T> {
|
||||||
|
|
||||||
|
public static final int DEFAULT_ROW_CAPACITY = 3;
|
||||||
|
|
||||||
|
private List<List<T>> rows = new ArrayList<List<T>>();
|
||||||
|
|
||||||
|
private int previousRowSize = -1;
|
||||||
|
|
||||||
|
private boolean frozen;
|
||||||
|
|
||||||
|
public TableModelBuilder<T> addRow() {
|
||||||
|
Assert.isTrue(!frozen, "TableModel has already been built, builder can't be altered anymore");
|
||||||
|
int nbRows = rows.size();
|
||||||
|
if (previousRowSize != -1) {
|
||||||
|
int currentRowSize = rows.get(nbRows - 1).size();
|
||||||
|
Assert.isTrue(currentRowSize == previousRowSize,
|
||||||
|
"Can't switch to next row, as the current one does not have as many elements as the previous one");
|
||||||
|
}
|
||||||
|
if (rows.size() > 0) {
|
||||||
|
previousRowSize = rows.get(0).size();
|
||||||
|
}
|
||||||
|
rows.add(new ArrayList<T>(previousRowSize == -1 ? DEFAULT_ROW_CAPACITY : previousRowSize));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableModelBuilder<T> addValue(T value) {
|
||||||
|
Assert.isTrue(!frozen, "TableModel has already been built, builder can't be altered anymore");
|
||||||
|
if (previousRowSize != -1 && rows.get(rows.size() - 1).size() == previousRowSize) {
|
||||||
|
throw new IllegalArgumentException("Can't add another value to current row");
|
||||||
|
}
|
||||||
|
rows.get(rows.size() - 1).add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableModel build() {
|
||||||
|
frozen = true;
|
||||||
|
return new TableModel() {
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return rows.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return rows.isEmpty() ? 0 : rows.get(0).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue(int row, int column) {
|
||||||
|
return rows.get(row).get(column);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class used to create and configure typical Tables.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public class Tables {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install all the necessary formatters, aligners, etc for key-value rendering of Maps.
|
||||||
|
*
|
||||||
|
* @param builder the builder to configure
|
||||||
|
* @param delimiter the delimiter to apply between keys and values
|
||||||
|
* @return buider for method chaining
|
||||||
|
*/
|
||||||
|
public static TableBuilder configureKeyValueRendering(TableBuilder builder, String delimiter) {
|
||||||
|
return builder.on(CellMatchers.ofType(Map.class))
|
||||||
|
.addFormatter(new MapFormatter(delimiter))
|
||||||
|
.addAligner(new KeyValueHorizontalAligner(delimiter.trim()))
|
||||||
|
.addSizer(new KeyValueSizeConstraints(delimiter))
|
||||||
|
.addWrapper(new KeyValueTextWrapper(delimiter)).and();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.shell.table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A strategy for applying text wrapping/cropping given a cell width.
|
||||||
|
*
|
||||||
|
* @author Eric Bottard
|
||||||
|
*/
|
||||||
|
public interface TextWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a list of lines where each line length MUST be equal to {@code columnWidth} (padding with spaces if
|
||||||
|
* appropriate). There is no constraint on the number of lines returned however (typically, will be greater than
|
||||||
|
* the input number if wrapping occurred).
|
||||||
|
*
|
||||||
|
* @param original the text in its original form
|
||||||
|
* @param columnWidth the width to conform to
|
||||||
|
*/
|
||||||
|
String[] wrap(String[] original, int columnWidth);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user