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}