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}