001package ball.text; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: TextTable.java 6118 2020-06-04 19:31:45Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/text/TextTable.java $ 007 * %% 008 * Copyright (C) 2008 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import ball.activation.ReaderWriterDataSource; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.PrintWriter; 027import java.util.Arrays; 028import javax.swing.event.TableModelEvent; 029import javax.swing.event.TableModelListener; 030import javax.swing.table.AbstractTableModel; 031import javax.swing.table.TableModel; 032import lombok.NoArgsConstructor; 033import lombok.ToString; 034import org.apache.commons.lang3.StringUtils; 035 036import static org.apache.commons.lang3.StringUtils.EMPTY; 037import static org.apache.commons.lang3.StringUtils.SPACE; 038import static org.apache.commons.lang3.StringUtils.isBlank; 039import static org.apache.commons.lang3.StringUtils.repeat; 040import static org.apache.commons.lang3.StringUtils.rightPad; 041import static org.apache.commons.lang3.StringUtils.stripEnd; 042 043/** 044 * Text-based {@link javax.swing.JTable} implementation. 045 * 046 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 047 * @version $Revision: 6118 $ 048 */ 049public class TextTable extends ReaderWriterDataSource { 050 private final TableModel model; 051 private int[] tabs = new int[] { }; 052 private int[] widths = new int[] { }; 053 054 /** 055 * Sole constructor. 056 * 057 * @param model The {@link TextTable}'s {@link TableModel}. 058 * @param tabs The preferred tab stops. 059 */ 060 public TextTable(TableModel model, int... tabs) { 061 super(null, TEXT_PLAIN); 062 063 this.model = model; 064 065 getModel().addTableModelListener(new ModelListenerImpl()); 066 } 067 068 /** 069 * Method to get {@link.this} {@link TextTable}'s {@link TableModel}. 070 * 071 * @return The {@link TableModel}. 072 */ 073 public TableModel getModel() { return model; } 074 075 /** 076 * Method to render the {@link TextTable}. 077 */ 078 protected void render() { 079 TableModel model = getModel(); 080 081 if (model instanceof AbstractTableModel) { 082 ((AbstractTableModel) model).fireTableDataChanged(); 083 } 084 085 tabs = Arrays.copyOf(tabs, model.getColumnCount()); 086 widths = Arrays.copyOf(widths, model.getColumnCount()); 087 088 for (int x = 0; x < widths.length; x += 1) { 089 widths[x] = length(model.getColumnName(x)); 090 091 for (int y = 0, n = model.getRowCount(); y < n; y += 1) { 092 Object object = model.getValueAt(y, x); 093 String string = 094 (object != null) ? object.toString() : null; 095 096 widths[x] = Math.max(widths[x], length(string)); 097 } 098 } 099 100 try (PrintWriter out = getPrintWriter()) { 101 StringBuilder header = line(fill(header())); 102 103 if (! isBlank(header)) { 104 out.println(stripEnd(header.toString(), null)); 105 out.println(repeat('-', header.length())); 106 } 107 108 for (int y = 0, n = model.getRowCount(); y < n; y += 1) { 109 out.println(stripEnd(line(fill(format(row(y)))).toString(), null)); 110 } 111 } catch (IOException exception) { 112 throw new IllegalStateException(exception); 113 } 114 } 115 116 private String[] header() { 117 String[] header = new String[getModel().getColumnCount()]; 118 119 for (int x = 0; x < header.length; x += 1) { 120 String string = getModel().getColumnName(x); 121 122 header[x] = (string != null) ? string : EMPTY; 123 } 124 125 return header; 126 } 127 128 private Object[] row(int y) { 129 Object[] row = new Object[getModel().getColumnCount()]; 130 131 for (int x = 0; x < row.length; x += 1) { 132 row[x] = getModel().getValueAt(y, x); 133 } 134 135 return row; 136 } 137 138 private String[] format(Object... row) { 139 String[] strings = new String[row.length]; 140 141 for (int x = 0; x < strings.length; x += 1) { 142 strings[x] = String.valueOf(row[x]); 143 } 144 145 return strings; 146 } 147 148 private String[] fill(String... row) { 149 String[] strings = new String[row.length]; 150 151 for (int x = 0; x < strings.length; x += 1) { 152 strings[x] = rightPad(row[x], widths[x], SPACE); 153 } 154 155 return strings; 156 } 157 158 private StringBuilder line(String[] row) { 159 StringBuilder line = new StringBuilder(); 160 161 for (int x = 0; x < row.length; x += 1) { 162 if (x > 0) { 163 line.append(SPACE); 164 } 165 166 while (line.length() < tabs[x]) { 167 line.append(SPACE); 168 } 169 170 line.append(row[x]); 171 } 172 173 return line; 174 } 175 176 @Override 177 public InputStream getInputStream() throws IOException { 178 if (! (length() > 0)) { 179 render(); 180 } 181 182 return super.getInputStream(); 183 } 184 185 private static int length(CharSequence sequence) { 186 return (sequence != null) ? sequence.length() : 0; 187 } 188 189 @NoArgsConstructor @ToString 190 private class ModelListenerImpl implements TableModelListener { 191 @Override 192 public void tableChanged(TableModelEvent event) { clear(); } 193 } 194}