/* * Copyright 2014 Jocki Hendry * * 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 simple.escp.fill; import simple.escp.data.DataSource; import simple.escp.data.DataSources; import simple.escp.dom.Line; import simple.escp.dom.Report; import simple.escp.dom.TableColumn; import simple.escp.dom.line.TableLine; import simple.escp.dom.line.TextLine; import simple.escp.placeholder.Placeholder; import simple.escp.placeholder.ScriptPlaceholder; import simple.escp.util.EscpUtil; import simple.escp.util.StringUtil; import javax.script.ScriptContext; import javax.script.ScriptEngine; import java.util.Collection; import java.util.List; import java.util.logging.Logger; /** * A helper class used internally by {@link simple.escp.fill.TableFillJob}. */ public class TableFillHelper { private static final Logger LOG = Logger.getLogger("simple.escp"); private Report report; private TableLine tableLine; private Collection source; private ScriptEngine scriptEngine; private WrappedBuffer wrappedBuffer; private Placeholder[] placeholders; /** * Create a new instance of this helper class. * * @param report <code>flush()</code> method will add new <code>TextLine</code> to this <code>Report</code>. * @param scriptEngine the <code>ScriptEngine</code> for evaluating placeholders. * @param tableLine the <code>TableLine</code> to be filled. * @param source source for <code>tableLine</code>. */ public TableFillHelper(Report report, ScriptEngine scriptEngine, TableLine tableLine, Collection source) { this.report = report; this.scriptEngine = scriptEngine; this.tableLine = tableLine; this.source = source; this.wrappedBuffer = new WrappedBuffer(); preparePlaceholders(); } /** * Read information from <code>tableLine</code> and creates instance of <code>ScriptPlaceholder</code> * for every columns. */ private void preparePlaceholders() { placeholders = new ScriptPlaceholder[tableLine.getNumberOfColumns()]; LOG.fine("Preparing " + placeholders.length + " placeholders"); for (int i = 0; i < tableLine.getNumberOfColumns(); i++) { TableColumn column = tableLine.getColumnAt(i + 1); placeholders[i] = new ScriptPlaceholder(column.getText(), scriptEngine); if (!column.isWrap()) { placeholders[i].setWidth(column.getWidth() - (tableLine.isDrawBorder() ? 1 : 0)); } } } /** * Add new text to a string builder that represents the content of a line. * * @param result new text will be appended to this builder. * @param text the content that will be appended. * @param index the position of this column (start from <code>0</code> for the left-most column). This value * is required to determine what borders to print if table border is enabled. */ private void appendLine(StringBuilder result, String text, int index) { if (index == 0 && tableLine.isDrawBorder()) { result.append(EscpUtil.CP347_LIGHT_VERTICAL); } result.append(text); if (tableLine.isDrawBorder()) { result.append(EscpUtil.CP347_LIGHT_VERTICAL); } } /** * Create line separator. * * @return a <code>String</code> that represents line separator for this table. */ private String lineSeparator() { StringBuilder result = new StringBuilder(); if (tableLine.isDrawBorder()) { result.append(EscpUtil.CP347_LIGHT_VERTICAL_RIGHT); } for (int i = 1; i <= tableLine.getNumberOfColumns(); i++) { TableColumn column = tableLine.getColumnAt(i); int columnWidth = column.getWidth() - (tableLine.isDrawBorder() ? 1 : 0); for (int j = 0; j < columnWidth; j++) { result.append(EscpUtil.CP347_LIGHT_HORIZONTAL); } if (tableLine.isDrawBorder() && (i != tableLine.getNumberOfColumns())) { result.append(EscpUtil.CP347_LIGHT_VERTICAL_HORIZONTAL); } } if (tableLine.isDrawBorder()) { result.append(EscpUtil.CP347_LIGHT_VERTICAL_LEFT); } return result.toString(); } /** * Execute this helper function. * * @return a collection of <code>Line</code>. */ public List<Line> process() { int rowNumber = 1; for (Object entry: source) { LOG.fine("Row number [" + rowNumber + "] Source [" + entry + "]"); StringBuilder text = new StringBuilder(); DataSource[] entryDataSources = DataSources.from(new Object[]{entry}); DataSourceBinding lineContext = new DataSourceBinding(entryDataSources); lineContext.put("row", rowNumber); scriptEngine.setBindings(lineContext, ScriptContext.ENGINE_SCOPE); // Prepare values before actually add them to the result. String[] values = new String[tableLine.getNumberOfColumns()]; for (int i = 0; i < tableLine.getNumberOfColumns(); i++) { lineContext.put("col", i + 1); TableColumn column = tableLine.getColumnAt(i + 1); String value = placeholders[i].getValueAsString(entryDataSources); if (column.isWrap()) { values[i] = wrappedBuffer.add(i, value); } else { values[i] = value; } } // Add calculated value to the result boolean needUnderline = wrappedBuffer.isEmpty(); for (int i = 0; i < tableLine.getNumberOfColumns(); i++) { if (needUnderline && tableLine.isDrawUnderlineSeparator()) { appendLine(text, EscpUtil.escSelectUnderline() + values[i] + EscpUtil.escCancelUnderline(), i); } else { appendLine(text, values[i], i); } } report.append(new TextLine(text.toString()), false); wrappedBuffer.flush(); if (rowNumber < source.size() && tableLine.isDrawLineSeparator()) { report.append(new TextLine(lineSeparator()), false); } rowNumber++; } return report.getFlatLines(); } /** * Retrieve the <code>WrappedBuffer</code> for this helper. * * @return an instnace of <code>WrappedBuffer</code> used by this helper. */ public WrappedBuffer getWrappedBuffer() { return wrappedBuffer; } /** * This is a helper class used internally by <code>TableFillHelper</code>. This class will handle values * that need to be wrapped to the next line. */ public class WrappedBuffer { private String[] buffer; private int[] width; /** * Create a new instance of <code>WrappedBuffer</code>. */ public WrappedBuffer() { buffer = new String[tableLine.getNumberOfColumns()]; width = new int[tableLine.getNumberOfColumns()]; for (int i = 0; i < tableLine.getNumberOfColumns(); i++) { TableColumn column = tableLine.getColumnAt(i + 1); width[i] = column.getWidth() - (tableLine.isDrawBorder() ? 1 : 0); } } /** * Add a new text to a column. If the new text need to be wrapped, this method will return * a truncated version of <code>value</code> that fits for current line. It will store the rest part of * <code>value</code> to <code>buffer</code> that can be consumed later by calling {@link #consume(int)}. * If <code>value</code> fits for this column, it will return the left-aligned version of <code>value</code>. * * @param index the column index, starts from <code>0</code> for the left-most column. * @param value the value for the specified column. * @return the truncated or the left-aligned <code>value</code>. */ public String add(int index, String value) { if (value.length() <= width[index]) { return StringUtil.alignLeft(value, width[index]); } else { buffer[index] = value.substring(width[index]); return value.substring(0, width[index]); } } /** * Get the content of buffer for a column. * * @param index the column index, starts from <code>0</code> for the left-most column. * @return content of buffer for column at <code>index</code>. */ public String getBuffer(int index) { return buffer[index]; } /** * Get the width of a column. * * @param index the column index, starts from <code>0</code> for the left-most column. * @return the width for column at <code>index</code>. */ public int getWidth(int index) { return width[index]; } /** * Read and remove the value in the buffer that fits for a line. If buffer is empty, this method will * return empty spaces as much as the column's width. * * @param index the column index, starts from <code>0</code> for the left-most column. * @return the value from buffer that fits for a line. */ public String consume(int index) { String value = getBuffer(index); if (value == null) { return StringUtil.alignLeft("", getWidth(index)); } if (value.length() <= getWidth(index)) { buffer[index] = null; return StringUtil.alignLeft(value, getWidth(index)); } else { return add(index, value); } } /** * Check if this buffer is empty. * * @return <code>true</code> if buffer is empty. */ public boolean isEmpty() { for (String s : buffer) { if (s != null) { return false; } } return true; } /** * Remove all column's buffers. */ public void clear() { for (int i = 0; i < buffer.length; i++) { buffer[i] = null; } } /** * Process all column's buffers and insert them as <code>TextLine</code> to current <code>report</code>. */ public void flush() { while (!isEmpty()) { StringBuilder result = new StringBuilder(); // Store the value first so we can determine to draw underline or not String[] values = new String[buffer.length]; for (int i = 0; i < buffer.length; i++) { values[i] = consume(i); } for (int i = 0; i < buffer.length; i++) { if (isEmpty() && tableLine.isDrawUnderlineSeparator()) { appendLine(result, EscpUtil.escSelectUnderline() + values[i] + EscpUtil.escCancelUnderline(), i); } else { appendLine(result, values[i], i); } } report.append(new TextLine(result.toString()), false); } clear(); } } }