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