From ea2da23dac3a50738b47eb01777566b3fd4d0811 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Thu, 29 Mar 2018 12:52:23 +0200 Subject: [PATCH] [#7353] Let Cursor extend Formattable --- jOOQ/src/main/java/org/jooq/Cursor.java | 2 +- .../java/org/jooq/impl/AbstractCursor.java | 1483 +++++++++++++++++ .../java/org/jooq/impl/AbstractRecord.java | 6 +- .../main/java/org/jooq/impl/CursorImpl.java | 4 +- .../main/java/org/jooq/impl/ResultImpl.java | 1402 +--------------- 5 files changed, 1493 insertions(+), 1404 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/AbstractCursor.java diff --git a/jOOQ/src/main/java/org/jooq/Cursor.java b/jOOQ/src/main/java/org/jooq/Cursor.java index 58145cd59b..6168a27855 100644 --- a/jOOQ/src/main/java/org/jooq/Cursor.java +++ b/jOOQ/src/main/java/org/jooq/Cursor.java @@ -73,7 +73,7 @@ import org.jooq.impl.DefaultRecordMapper; * @param The cursor's record type * @author Lukas Eder */ -public interface Cursor extends Iterable , AutoCloseable { +public interface Cursor extends Iterable, Formattable , AutoCloseable { /** * Get this cursor's row type. diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractCursor.java b/jOOQ/src/main/java/org/jooq/impl/AbstractCursor.java new file mode 100644 index 0000000000..8f2da6c65b --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractCursor.java @@ -0,0 +1,1483 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static java.lang.Math.max; +import static java.lang.Math.min; +import static org.jooq.XMLFormat.RecordFormat.COLUMN_NAME_ELEMENTS; +import static org.jooq.XMLFormat.RecordFormat.VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE; +import static org.jooq.impl.DSL.insertInto; +import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.table; +import static org.jooq.tools.StringUtils.abbreviate; +import static org.jooq.tools.StringUtils.leftPad; +import static org.jooq.tools.StringUtils.rightPad; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; + +import javax.xml.bind.DatatypeConverter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.jooq.CSVFormat; +import org.jooq.ChartFormat; +import org.jooq.ChartFormat.Display; +import org.jooq.Configuration; +import org.jooq.Constants; +import org.jooq.Cursor; +import org.jooq.DSLContext; +import org.jooq.EnumType; +import org.jooq.Field; +import org.jooq.Formattable; +import org.jooq.JSONFormat; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.Schema; +import org.jooq.TXTFormat; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.TableRecord; +import org.jooq.XMLFormat; +import org.jooq.exception.IOException; +import org.jooq.tools.StringUtils; +import org.jooq.tools.json.JSONValue; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * @author Lukas Eder + */ +abstract class AbstractCursor implements Formattable, Iterable { + + final Fields fields; + Configuration configuration; + + AbstractCursor(Configuration configuration, Fields fields) { + this.configuration = configuration; + this.fields = fields; + } + + @Override + public final String format() { + return format(TXTFormat.DEFAULT); + } + + @Override + public final String format(int maxRecords) { + return format(TXTFormat.DEFAULT.maxRows(maxRecords)); + } + + @Override + public final String format(TXTFormat format) { + StringWriter writer = new StringWriter(); + format(writer, format); + return writer.toString(); + } + + @Override + public final void format(OutputStream stream) { + format(new OutputStreamWriter(stream)); + } + + @Override + public final void format(OutputStream stream, int maxRecords) { + format(new OutputStreamWriter(stream), maxRecords); + } + + @Override + public final void format(OutputStream stream, TXTFormat format) { + format(new OutputStreamWriter(stream), format); + } + + @Override + public final void format(Writer writer) { + format(writer, TXTFormat.DEFAULT); + } + + @Override + public final void format(Writer writer, int maxRecords) { + format(writer, TXTFormat.DEFAULT.maxRows(maxRecords)); + } + + @Override + public final void format(Writer writer, TXTFormat format) { + try { + + // Numeric columns have greater max width because values are aligned + final int NUM_COL_MAX_WIDTH = format.maxColWidth() == Integer.MAX_VALUE ? Integer.MAX_VALUE : 2 * format.maxColWidth(); + + // The max number of records that will be considered for formatting purposes + final int MAX_RECORDS = min(50, format.maxRows()); + final Deque buffer = new ArrayDeque(); + final Iterator it = iterator(); + + // Buffer some rows for formatting purposes + for (int i = 0; i < MAX_RECORDS && it.hasNext(); i++) + buffer.offer(it.next()); + + // Get max decimal places for numeric type columns + final int[] decimalPlaces = new int[fields.fields.length]; + final int[] widths = new int[fields.fields.length]; + + for (int index = 0; index < fields.fields.length; index++) { + if (Number.class.isAssignableFrom(fields.fields[index].getType())) { + List decimalPlacesList = new ArrayList(); + + // Initialize + decimalPlacesList.add(0); + + // Collect all decimal places for the column values + for (R record : buffer) + decimalPlacesList.add(decimalPlaces(format0(record.get(index), record.changed(index), true))); + + // Find max + decimalPlaces[index] = Collections.max(decimalPlacesList); + } + } + + // Get max column widths + int colMaxWidth; + for (int index = 0; index < fields.fields.length; index++) { + + // Is number column? + boolean isNumCol = Number.class.isAssignableFrom(fields.fields[index].getType()); + + colMaxWidth = isNumCol ? NUM_COL_MAX_WIDTH : format.maxColWidth(); + + // Collect all widths for the column + List widthList = new ArrayList(); + + // Add column name width first + widthList.add(min(colMaxWidth, max(format.minColWidth(), fields.fields[index].getName().length()))); + + // Add column values width + for (R record : buffer) { + String value = format0(record.get(index), record.changed(index), true); + + // Align number values before width is calculated + if (isNumCol) + value = alignNumberValue(decimalPlaces[index], value); + + widthList.add(min(colMaxWidth, value.length())); + } + + // Find max + widths[index] = Collections.max(widthList); + } + + // Begin the writing + // --------------------------------------------------------------------- + + // Write top line + if (format.horizontalTableBorder()) + formatHorizontalLine(writer, format, widths); + + // Write headers + if (format.verticalTableBorder()) + writer.append('|'); + + for (int index = 0; index < fields.fields.length; index++) { + if (index > 0) + if (format.verticalCellBorder()) + writer.append('|'); + else + writer.append(' '); + + String padded; + + if (Number.class.isAssignableFrom(fields.fields[index].getType())) + padded = leftPad(fields.fields[index].getName(), widths[index]); + else + padded = rightPad(fields.fields[index].getName(), widths[index]); + + writer.append(abbreviate(padded, widths[index])); + } + + if (format.verticalTableBorder()) + writer.append('|'); + + writer.append('\n'); + + // Write separator + if (format.horizontalHeaderBorder()) + formatHorizontalLine(writer, format, widths); + + // Write records + int i; + + recordLoop: + for (i = 0; i < format.maxRows(); i++) { + R record = buffer.pollFirst(); + + if (record == null) + if (it.hasNext()) + record = it.next(); + else + break recordLoop; + + // Write separator + if (i > 0 && format.horizontalCellBorder()) + formatHorizontalLine(writer, format, widths); + + if (format.verticalTableBorder()) + writer.append('|'); + + for (int index = 0; index < fields.fields.length; index++) { + if (index > 0) + if (format.verticalCellBorder()) + writer.append('|'); + else + writer.append(' '); + + String value = + StringUtils.replace( + StringUtils.replace( + StringUtils.replace( + format0(record.get(index), record.changed(index), true), "\n", "{lf}" + ), "\r", "{cr}" + ), "\t", "{tab}" + ); + + String padded; + if (Number.class.isAssignableFrom(fields.fields[index].getType())) { + // Align number value before left pad + value = alignNumberValue(decimalPlaces[index], value); + + // Left pad + padded = leftPad(value, widths[index]); + } + else { + // Right pad + padded = rightPad(value, widths[index]); + } + + writer.append(abbreviate(padded, widths[index])); + } + + if (format.verticalTableBorder()) + writer.append('|'); + + writer.append('\n'); + } + + // Write bottom line + if (format.horizontalTableBorder() && i > 0) + formatHorizontalLine(writer, format, widths); + + // Write truncation message, if applicable + if (it.hasNext()) { + if (format.verticalTableBorder()) + writer.append('|'); + + writer.append("...record(s) truncated...\n"); + } + + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing TEXT", e); + } + } + + private final void formatHorizontalLine(Writer writer, TXTFormat format, final int[] widths) throws java.io.IOException { + if (format.verticalTableBorder()) + if (format.intersectLines()) + writer.append('+'); + else + writer.append('-'); + + for (int index = 0; index < fields.fields.length; index++) { + if (index > 0) + if (format.verticalCellBorder()) + if (format.intersectLines()) + writer.append('+'); + else + writer.append('-'); + else + writer.append(' '); + + writer.append(rightPad("", widths[index], "-")); + } + + if (format.verticalTableBorder()) + if (format.intersectLines()) + writer.append('+'); + else + writer.append('-'); + + writer.append('\n'); + } + + private static final String alignNumberValue(Integer columnDecimalPlaces, String value) { + if (!"{null}".equals(value) && columnDecimalPlaces != 0) { + int decimalPlaces = decimalPlaces(value); + int rightPadSize = value.length() + columnDecimalPlaces - decimalPlaces; + + if (decimalPlaces == 0) { + // If integer value, add one for decimal point + value = rightPad(value, rightPadSize + 1); + } + else { + value = rightPad(value, rightPadSize); + } + } + + return value; + } + + private static final int decimalPlaces(String value) { + int decimalPlaces = 0; + + int dotIndex = value.indexOf("."); + if (dotIndex != -1) + decimalPlaces = value.length() - dotIndex - 1; + + return decimalPlaces; + } + + @Override + public final String formatCSV() { + return formatCSV(true); + } + + @Override + public final String formatCSV(boolean header) { + StringWriter writer = new StringWriter(); + formatCSV(writer, header); + return writer.toString(); + } + + @Override + public final void formatCSV(OutputStream stream) { + formatCSV(stream, true); + } + + @Override + public final void formatCSV(OutputStream stream, boolean header) { + formatCSV(new OutputStreamWriter(stream), header); + } + + @Override + public final void formatCSV(Writer writer) { + formatCSV(writer, true); + } + + @Override + public final void formatCSV(Writer writer, boolean header) { + formatCSV(writer, header, ',', "\"\""); + } + + @Override + public final String formatCSV(char delimiter) { + return formatCSV(true, delimiter); + } + + @Override + public final String formatCSV(boolean header, char delimiter) { + StringWriter writer = new StringWriter(); + formatCSV(writer, delimiter); + return writer.toString(); + } + + @Override + public final void formatCSV(OutputStream stream, char delimiter) { + formatCSV(stream, true, delimiter); + } + + @Override + public final void formatCSV(OutputStream stream, boolean header, char delimiter) { + formatCSV(new OutputStreamWriter(stream), delimiter); + } + + @Override + public final void formatCSV(Writer writer, char delimiter) { + formatCSV(writer, true, delimiter); + } + + @Override + public final void formatCSV(Writer writer, boolean header, char delimiter) { + formatCSV(writer, header, delimiter, "\"\""); + } + + @Override + public final String formatCSV(char delimiter, String nullString) { + return formatCSV(true, delimiter, nullString); + } + + @Override + public final String formatCSV(boolean header, char delimiter, String nullString) { + StringWriter writer = new StringWriter(); + formatCSV(writer, header, delimiter, nullString); + return writer.toString(); + } + + @Override + public final String formatCSV(CSVFormat format) { + StringWriter writer = new StringWriter(); + formatCSV(writer, format); + return writer.toString(); + } + + @Override + public final void formatCSV(OutputStream stream, char delimiter, String nullString) { + formatCSV(stream, true, delimiter, nullString); + } + + @Override + public final void formatCSV(OutputStream stream, boolean header, char delimiter, String nullString) { + formatCSV(new OutputStreamWriter(stream), header, delimiter, nullString); + } + + @Override + public final void formatCSV(OutputStream stream, CSVFormat format) { + formatCSV(new OutputStreamWriter(stream), format); + } + + @Override + public final void formatCSV(Writer writer, char delimiter, String nullString) { + formatCSV(writer, true, delimiter, nullString); + } + + @Override + public final void formatCSV(Writer writer, boolean header, char delimiter, String nullString) { + formatCSV(writer, new CSVFormat().header(header).delimiter(delimiter).nullString(nullString)); + } + + @Override + public final void formatCSV(Writer writer, CSVFormat format) { + try { + if (format.header()) { + String sep1 = ""; + for (Field field : fields.fields) { + writer.append(sep1); + writer.append(formatCSV0(field.getName(), format)); + + sep1 = format.delimiter(); + } + + writer.append(format.newline()); + } + + for (Record record : this) { + String sep2 = ""; + + for (int index = 0; index < fields.fields.length; index++) { + writer.append(sep2); + writer.append(formatCSV0(record.getValue(index), format)); + + sep2 = format.delimiter(); + } + + writer.append(format.newline()); + } + + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing CSV", e); + } + } + + private final String formatCSV0(Object value, CSVFormat format) { + + // [#2741] TODO: This logic will be externalised in new SPI + // [#4746] Escape null and empty strings + if (value == null) + return format.nullString(); + + if ("".equals(value.toString())) + return format.emptyString(); + + String result = format0(value, false, false); + switch (format.quote()) { + case NEVER: + return result; + + case SPECIAL_CHARACTERS: + if (!StringUtils.containsAny(result, ',', ';', '\t', '"', '\n', '\r', '\'', '\\')) + return result; + + // no break + case ALWAYS: + default: + return format.quoteString() + + StringUtils.replace( + StringUtils.replace( + result, "\\", "\\\\" + ), format.quoteString(), format.quoteString() + format.quoteString() + ) + + format.quoteString(); + } + } + + @Override + public final String formatJSON() { + StringWriter writer = new StringWriter(); + formatJSON(writer); + return writer.toString(); + } + + @Override + public final String formatJSON(JSONFormat format) { + StringWriter writer = new StringWriter(); + formatJSON(writer, format); + return writer.toString(); + } + + @Override + public final void formatJSON(OutputStream stream) { + formatJSON(new OutputStreamWriter(stream)); + } + + @Override + public final void formatJSON(OutputStream stream, JSONFormat format) { + formatJSON(new OutputStreamWriter(stream), format); + } + + @Override + public final void formatJSON(Writer writer) { + formatJSON(writer, JSONFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final void formatJSON(Writer writer, JSONFormat format) { + try { + String separator; + int recordLevel = format.header() ? 2 : 1; + + if (format.header()) { + if (format.format()) + writer.append('{').append(format.newline()) + .append(format.indentString(1)).append("\"fields\": ["); + else + writer.append("{\"fields\":["); + + separator = ""; + + for (Field field : fields.fields) { + writer.append(separator); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(2)); + + writer.append('{'); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(3)); + + if (field instanceof TableField) { + Table table = ((TableField) field).getTable(); + + if (table != null) { + Schema schema = table.getSchema(); + + if (schema != null) { + writer.append("\"schema\":"); + + if (format.format()) + writer.append(' '); + + JSONValue.writeJSONString(schema.getName(), writer); + writer.append(','); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(3)); + } + + writer.append("\"table\":"); + + if (format.format()) + writer.append(' '); + + JSONValue.writeJSONString(table.getName(), writer); + writer.append(','); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(3)); + } + } + + writer.append("\"name\":"); + + if (format.format()) + writer.append(' '); + + JSONValue.writeJSONString(field.getName(), writer); + writer.append(','); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(3)); + + writer.append("\"type\":"); + + if (format.format()) + writer.append(' '); + + JSONValue.writeJSONString(field.getDataType().getTypeName().toUpperCase(), writer); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(2)); + + writer.append('}'); + separator = ","; + } + + if (format.format()) + writer.append(format.newline()).append(format.indentString(1)).append("],") + .append(format.newline()).append(format.indentString(1)).append("\"records\": "); + else + writer.append("],\"records\":"); + } + + writer.append('['); + separator = ""; + + switch (format.recordFormat()) { + case ARRAY: + for (Record record : this) { + writer.append(separator); + + if (format.format()) + writer.append(format.newline()); + + formatJSONArray0(record, fields, format, recordLevel, writer); + separator = ","; + } + + break; + case OBJECT: + for (Record record : this) { + writer.append(separator); + + if (format.format()) + writer.append(format.newline()); + + formatJSONMap0(record, fields, format, recordLevel, writer); + separator = ","; + } + + break; + default: + throw new IllegalArgumentException("Format not supported: " + format); + } + + if (format.format()) { + writer.append(format.newline()); + + if (format.header()) + writer.append(format.indentString(1)); + } + + writer.append(']'); + + if (format.header()) + writer.append(format.newline()).append('}'); + + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing JSON", e); + } + } + + private static final void formatJSON0(Object value, Writer writer) throws java.io.IOException { + + // [#2741] TODO: This logic will be externalised in new SPI + if (value instanceof byte[]) { + JSONValue.writeJSONString(DatatypeConverter.printBase64Binary((byte[]) value), writer); + } + + // [#6563] Arrays can be serialised natively in JSON + else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + writer.append('['); + + for (int i = 0; i < array.length; i++) { + if (i > 0) + writer.append(','); + + formatJSON0(array[i], writer); + } + + writer.append(']'); + } + + else { + JSONValue.writeJSONString(value, writer); + } + } + + static final void formatJSONMap0(Record record, Fields fields, JSONFormat format, int recordLevel, Writer writer) throws java.io.IOException { + String separator = ""; + + if (format.format()) + writer.append(format.indentString(recordLevel)).append('{'); + else + writer.append('{'); + + for (int index = 0; index < fields.fields.length; index++) { + writer.append(separator); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + + JSONValue.writeJSONString(record.field(index).getName(), writer); + writer.append(':'); + if (format.format()) + writer.append(' '); + + formatJSON0(record.get(index), writer); + separator = ","; + } + + if (format.format()) + writer.append(format.newline()).append(format.indentString(recordLevel)); + + writer.append('}'); + } + + static final void formatJSONArray0(Record record, Fields fields, JSONFormat format, int recordLevel, Writer writer) throws java.io.IOException { + String separator = ""; + + if (format.format()) + writer.append(format.indentString(recordLevel)).append('['); + else + writer.append('['); + + for (int index = 0; index < fields.fields.length; index++) { + writer.append(separator); + + if (format.format()) + writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + + formatJSON0(record.get(index), writer); + separator = ","; + } + + if (format.format()) + writer.append(format.newline()).append(format.indentString(recordLevel)); + + writer.append(']'); + } + + @Override + public final String formatXML() { + return formatXML(XMLFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final String formatXML(XMLFormat format) { + StringWriter writer = new StringWriter(); + formatXML(writer, format); + return writer.toString(); + } + + @Override + public final void formatXML(OutputStream stream) { + formatXML(stream, XMLFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final void formatXML(OutputStream stream, XMLFormat format) { + formatXML(new OutputStreamWriter(stream), format); + } + + @Override + public final void formatXML(Writer writer) { + formatXML(writer, XMLFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final void formatXML(Writer writer, XMLFormat format) { + String newline = format.newline(); + int recordLevel = format.header() ? 2 : 1; + + try { + writer.append(""); + + if (format.header()) { + writer.append(newline).append(format.indentString(1)).append(""); + + for (Field field : fields.fields) { + writer.append(newline).append(format.indentString(2)).append(" table = ((TableField) field).getTable(); + + if (table != null) { + Schema schema = table.getSchema(); + + if (schema != null) { + writer.append(" schema=\""); + writer.append(escapeXML(schema.getName())); + writer.append("\""); + } + + writer.append(" table=\""); + writer.append(escapeXML(table.getName())); + writer.append("\""); + } + } + + writer.append(" name=\""); + writer.append(escapeXML(field.getName())); + writer.append("\""); + writer.append(" type=\""); + writer.append(field.getDataType().getTypeName().toUpperCase()); + writer.append("\"/>"); + } + + writer.append(newline).append(format.indentString(1)).append(""); + writer.append(newline).append(format.indentString(1)).append(""); + } + + for (Record record : this) { + writer.append(newline).append(format.indentString(recordLevel)); + formatXMLRecord(writer, format, recordLevel, record, fields); + } + + if (format.header()) + writer.append(newline).append(format.indentString(1)).append(""); + + writer.append(newline).append(""); + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing XML", e); + } + } + + static final void formatXMLRecord( + Writer writer, + XMLFormat format, + int recordLevel, + Record record, + Fields fields + ) + throws java.io.IOException { + String newline = format.newline(); + + writer.append(""); + + for (int index = 0; index < fields.fields.length; index++) { + Object value = record.get(index); + + writer.append(newline).append(format.indentString(recordLevel + 1)); + String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS + ? escapeXML(fields.fields[index].getName()) + : "value"; + + writer.append("<" + tag); + if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) { + writer.append(" field=\""); + writer.append(escapeXML(fields.fields[index].getName())); + writer.append("\""); + } + + if (value == null) { + writer.append("/>"); + } + else { + writer.append(">"); + writer.append(escapeXML(format0(value, false, false))); + writer.append(""); + } + } + + writer.append(newline).append(format.indentString(recordLevel)).append(""); + } + + @Override + public final String formatChart() { + StringWriter writer = new StringWriter(); + formatChart(writer); + return writer.toString(); + } + + @Override + public final String formatChart(ChartFormat format) { + StringWriter writer = new StringWriter(); + formatChart(writer, format); + return writer.toString(); + } + + @Override + public final void formatChart(OutputStream stream) { + formatChart(new OutputStreamWriter(stream)); + } + + @Override + public final void formatChart(OutputStream stream, ChartFormat format) { + formatChart(new OutputStreamWriter(stream), format); + } + + @Override + public final void formatChart(Writer writer) { + formatChart(writer, ChartFormat.DEFAULT); + } + + @SuppressWarnings("unchecked") + @Override + public final void formatChart(Writer writer, ChartFormat format) { + Result result; + + if (this instanceof Result) + result = (Result) this; + else if (this instanceof Cursor) + result = ((Cursor) this).fetch(); + else + throw new IllegalStateException(); + + try { + DSLContext ctx = configuration.dsl(); + Field category = fields.field(format.category()); + TreeMap> groups = new TreeMap>(result.intoGroups(format.category())); + + if (!format.categoryAsText()) { + if (Date.class.isAssignableFrom(category.getType())) { + Date categoryMin = (Date) groups.firstKey(); + Date categoryMax = (Date) groups.lastKey(); + + for (Date i = categoryMin; i.before(categoryMax); i = new Date(i.getYear(), i.getMonth(), i.getDate() + 1)) + if (!groups.containsKey(i)) + groups.put(i, (Result) ctx.newResult(fields.fields)); + } + } + + List categories = new ArrayList(groups.keySet()); + + int categoryPadding = 1; + int categoryWidth = 0; + for (Object o : categories) + categoryWidth = Math.max(categoryWidth, ("" + o).length()); + + double axisMin = Double.POSITIVE_INFINITY; + double axisMax = Double.NEGATIVE_INFINITY; + + for (Result values : groups.values()) { + double sum = 0; + + for (int i = 0; i < format.values().length; i++) { + if (format.display() == Display.DEFAULT) + sum = 0; + + for (Record r : values) + sum = sum + r.get(format.values()[i], double.class); + + if (sum < axisMin) + axisMin = sum; + + if (sum > axisMax) + axisMax = sum; + } + } + + int verticalLegendWidth = format.showVerticalLegend() + ? Math.max( + format.numericFormat().format(axisMin).length(), + format.numericFormat().format(axisMax).length() + ) + : 0; + + int horizontalLegendHeight = format.showHorizontalLegend() ? 1 : 0; + + int verticalBorderWidth = format.showVerticalLegend() ? 1 : 0; + int horizontalBorderHeight = format.showHorizontalLegend() ? 1 : 0; + + int chartHeight = format.height() - horizontalLegendHeight - horizontalBorderHeight; + int chartWidth = format.width() - verticalLegendWidth - verticalBorderWidth; + + double barWidth = (double) chartWidth / groups.size(); + double axisStep = (axisMax - axisMin) / (chartHeight - 1); + + for (int y = chartHeight - 1; y >= 0; y--) { + double axisLegend = axisMax - (axisStep * (chartHeight - 1 - y)); + double axisLegendPercent = (axisLegend - axisMin) / (axisMax - axisMin); + + if (format.showVerticalLegend()) { + String axisLegendString = (format.display() == Display.HUNDRED_PERCENT_STACKED) + ? format.numericFormat().format(axisLegendPercent * 100.0) + "%" + : format.numericFormat().format(axisLegend); + + for (int x = axisLegendString.length(); x < verticalLegendWidth; x++) + writer.write(' '); + + writer.write(axisLegendString); + + for (int x = 0; x < verticalBorderWidth; x++) + writer.write('|'); + } + + for (int x = 0; x < chartWidth; x++) { + int index = (int) (x / barWidth); + + Result group = groups.get(categories.get(index)); + double[] values = new double[format.values().length]; + + for (Record record : group) + for (int i = 0; i < values.length; i++) + values[i] = values[i] + record.get(format.values()[i], double.class); + + if (format.display() == Display.STACKED || format.display() == Display.HUNDRED_PERCENT_STACKED) + for (int i = 1; i < values.length; i++) + values[i] = values[i] + values[i - 1]; + + if (format.display() == Display.HUNDRED_PERCENT_STACKED) + for (int i = 0; i < values.length; i++) + values[i] = values[i] / values[values.length - 1]; + + int shadeIndex = -1; + for (int i = values.length - 1; i >= 0; i--) + if ((format.display() == Display.HUNDRED_PERCENT_STACKED ? axisLegendPercent : axisLegend) > values[i]) + break; + else + shadeIndex = i; + + if (shadeIndex == -1) + writer.write(' '); + else + writer.write(format.shades()[shadeIndex % format.shades().length]); + } + + writer.write(format.newline()); + } + + if (format.showHorizontalLegend()) { + for (int y = 0; y < horizontalBorderHeight; y++) { + if (format.showVerticalLegend()) { + for (int x = 0; x < verticalLegendWidth; x++) + writer.write('-'); + + for (int x = 0; x < verticalBorderWidth; x++) + writer.write('+'); + } + + for (int x = 0; x < chartWidth; x++) + writer.write('-'); + + writer.write(format.newline()); + } + + for (int y = 0; y < horizontalLegendHeight; y++) { + if (format.showVerticalLegend()) { + for (int x = 0; x < verticalLegendWidth; x++) + writer.write(' '); + + for (int x = 0; x < verticalBorderWidth; x++) + writer.write('|'); + } + + double rounding = 0.0; + for (double x = 0.0; x < chartWidth;) { + String label = "" + categories.get((int) (x / barWidth)); + int length = label.length(); + + double padding = Math.max(categoryPadding, (barWidth - length) / 2); + + rounding = (rounding + padding - Math.floor(padding)) % 1; + x = x + (padding + rounding); + for (int i = 0; i < (int) (padding + rounding); i++) + writer.write(' '); + + x = x + length; + if (x >= chartWidth) + break; + writer.write(label); + + rounding = (rounding + padding - Math.floor(padding)) % 1; + x = x + (padding + rounding); + for (int i = 0; i < (int) (padding + rounding); i++) + writer.write(' '); + } + + writer.write(format.newline()); + } + } + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing Chart", e); + } + } + + @Override + public final String formatInsert() { + StringWriter writer = new StringWriter(); + formatInsert(writer); + return writer.toString(); + } + + @Override + public final void formatInsert(OutputStream stream) { + formatInsert(new OutputStreamWriter(stream)); + } + + @Override + public final void formatInsert(Writer writer) { + formatInsert(writer, null, fields.fields); + } + + @Override + public final String formatInsert(Table table, Field... f) { + StringWriter writer = new StringWriter(); + formatInsert(writer, table, f); + return writer.toString(); + } + + @Override + public final void formatInsert(OutputStream stream, Table table, Field... f) { + formatInsert(new OutputStreamWriter(stream), table, f); + } + + @Override + public final void formatInsert(Writer writer, Table table, Field... f) { + DSLContext ctx = configuration.dsl(); + + try { + for (R record : this) { + if (table == null) + if (record instanceof TableRecord) + table = ((TableRecord) record).getTable(); + else + table = table(name("UNKNOWN_TABLE")); + + writer.append(ctx.renderInlined(insertInto(table, f).values(record.intoArray()))) + .append(";\n"); + } + + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing INSERTs", e); + } + } + + @Override + public final String formatHTML() { + StringWriter writer = new StringWriter(); + formatHTML(writer); + return writer.toString(); + } + + @Override + public final void formatHTML(OutputStream stream) { + formatHTML(new OutputStreamWriter(stream)); + } + + @Override + public final void formatHTML(Writer writer) { + try { + writer.append(""); + writer.append(""); + writer.append(""); + + for (Field field : fields.fields) { + writer.append(""); + } + + writer.append(""); + writer.append(""); + writer.append(""); + + for (Record record : this) { + writer.append(""); + + for (int index = 0; index < fields.fields.length; index++) { + writer.append(""); + } + + writer.append(""); + } + + writer.append(""); + writer.append("
"); + writer.append(escapeXML(field.getName())); + writer.append("
"); + writer.append(escapeXML(format0(record.getValue(index), false, true))); + writer.append("
"); + + writer.flush(); + } + catch (java.io.IOException e) { + throw new IOException("Exception while writing HTML", e); + } + } + + @Override + public final Document intoXML() { + return intoXML(XMLFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final Document intoXML(XMLFormat format) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + + Element eResult = document.createElement("result"); + + if (format.xmlns()) + eResult.setAttribute("xmlns", Constants.NS_EXPORT); + document.appendChild(eResult); + + Element eRecordParent = eResult; + + if (format.header()) { + Element eFields = document.createElement("fields"); + eResult.appendChild(eFields); + + for (Field field : fields.fields) { + Element eField = document.createElement("field"); + + if (field instanceof TableField) { + Table table = ((TableField) field).getTable(); + + if (table != null) { + Schema schema = table.getSchema(); + + if (schema != null) { + eField.setAttribute("schema", schema.getName()); + } + + eField.setAttribute("table", table.getName()); + } + } + + eField.setAttribute("name", field.getName()); + eField.setAttribute("type", field.getDataType().getTypeName().toUpperCase()); + eFields.appendChild(eField); + } + + Element eRecords = document.createElement("records"); + eResult.appendChild(eRecords); + eRecordParent = eRecords; + } + + for (Record record : this) { + Element eRecord = document.createElement("record"); + eRecordParent.appendChild(eRecord); + + for (int index = 0; index < fields.fields.length; index++) { + Field field = fields.fields[index]; + Object value = record.get(index); + + String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS + ? escapeXML(fields.fields[index].getName()) + : "value"; + + Element eValue = document.createElement(tag); + + if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) + eValue.setAttribute("field", field.getName()); + eRecord.appendChild(eValue); + + if (value != null) { + eValue.setTextContent(format0(value, false, false)); + } + } + } + + return document; + } + catch (ParserConfigurationException ignore) { + throw new RuntimeException(ignore); + } + } + + @Override + public final H intoXML(H handler) throws SAXException { + return intoXML(handler, XMLFormat.DEFAULT_FOR_RESULTS); + } + + @Override + public final H intoXML(H handler, XMLFormat format) throws SAXException { + Attributes empty = new AttributesImpl(); + + handler.startDocument(); + + if (format.xmlns()) + handler.startPrefixMapping("", Constants.NS_EXPORT); + + handler.startElement("", "", "result", empty); + if (format.header()) { + handler.startElement("", "", "fields", empty); + + for (Field field : fields.fields) { + AttributesImpl attrs = new AttributesImpl(); + + if (field instanceof TableField) { + Table table = ((TableField) field).getTable(); + + if (table != null) { + Schema schema = table.getSchema(); + + if (schema != null) { + attrs.addAttribute("", "", "schema", "CDATA", schema.getName()); + } + + attrs.addAttribute("", "", "table", "CDATA", table.getName()); + } + } + + attrs.addAttribute("", "", "name", "CDATA", field.getName()); + attrs.addAttribute("", "", "type", "CDATA", field.getDataType().getTypeName().toUpperCase()); + + handler.startElement("", "", "field", attrs); + handler.endElement("", "", "field"); + } + + handler.endElement("", "", "fields"); + handler.startElement("", "", "records", empty); + } + + for (Record record : this) { + handler.startElement("", "", "record", empty); + + for (int index = 0; index < fields.fields.length; index++) { + Field field = fields.fields[index]; + Object value = record.get(index); + + String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS + ? escapeXML(fields.fields[index].getName()) + : "value"; + + AttributesImpl attrs = new AttributesImpl(); + + if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) + attrs.addAttribute("", "", "field", "CDATA", field.getName()); + + handler.startElement("", "", tag, attrs); + + if (value != null) { + char[] chars = format0(value, false, false).toCharArray(); + handler.characters(chars, 0, chars.length); + } + + handler.endElement("", "", tag); + } + + handler.endElement("", "", "record"); + } + + if (format.header()) + handler.endElement("", "", "records"); + + if (format.xmlns()) + handler.endPrefixMapping(""); + + handler.endDocument(); + return handler; + } + + /** + * @param value The value to be formatted + * @param visual Whether the formatted output is to be consumed visually + * (HTML, TEXT) or by a machine (CSV, JSON, XML) + */ + private static final String format0(Object value, boolean changed, boolean visual) { + + // [#2741] TODO: This logic will be externalised in new SPI + String formatted = changed && visual ? "*" : ""; + + if (value == null) { + formatted += visual ? "{null}" : null; + } + else if (value.getClass() == byte[].class) { + formatted += DatatypeConverter.printBase64Binary((byte[]) value); + } + else if (value.getClass().isArray()) { + // [#6545] Nested arrays + if (value.getClass().getComponentType().isArray()) + formatted += Arrays.deepToString((Object[]) value); + else + formatted += Arrays.toString((Object[]) value); + } + else if (value instanceof EnumType) { + formatted += ((EnumType) value).getLiteral(); + } + else if (value instanceof Record) { + formatted += ((Record) value).valuesRow().toString(); + } + + // [#5238] Oracle DATE is really a TIMESTAMP(0)... + else if (value instanceof Date) { + String date = value.toString(); + + if (Date.valueOf(date).equals(value)) + formatted += date; + else + formatted += new Timestamp(((Date) value).getTime()); + } + else { + formatted += value.toString(); + } + + return formatted; + } + + private static final String escapeXML(String string) { + return StringUtils.replaceEach(string, + new String[] { "\"", "'", "<", ">", "&" }, + new String[] { """, "'", "<", ">", "&"}); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java index cb2274b230..62c5513119 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java @@ -988,10 +988,10 @@ abstract class AbstractRecord extends AbstractStore implements Record { try { switch (format.recordFormat()) { case ARRAY: - ResultImpl.formatJSONArray0(this, fields.fields, format, 0, writer); + AbstractCursor.formatJSONArray0(this, fields.fields, format, 0, writer); break; case OBJECT: - ResultImpl.formatJSONMap0(this, fields.fields, format, 0, writer); + AbstractCursor.formatJSONMap0(this, fields.fields, format, 0, writer); break; default: throw new IllegalArgumentException("Format not supported: " + format); @@ -1035,7 +1035,7 @@ abstract class AbstractRecord extends AbstractStore implements Record { log.debug("XMLFormat.header currently not supported for Record.formatXML()"); try { - ResultImpl.formatXMLRecord(writer, format, 0, this, fields.fields); + AbstractCursor.formatXMLRecord(writer, format, 0, this, fields.fields); } catch (java.io.IOException e) { throw new IOException("Exception while writing XML", e); diff --git a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java index 8c7fd933bd..193d3b5831 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java @@ -95,7 +95,7 @@ import org.jooq.tools.jdbc.JDBCUtils; /** * @author Lukas Eder */ -final class CursorImpl implements Cursor { +final class CursorImpl extends AbstractCursor implements Cursor { private static final JooqLogger log = JooqLogger.getLogger(CursorImpl.class); @@ -126,6 +126,8 @@ final class CursorImpl implements Cursor { } CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field[] fields, int[] internIndexes, boolean keepStatement, boolean keepResultSet, Class type, int maxRows) { + super(ctx.configuration(), new Fields(fields)); + this.ctx = ctx; this.listener = (listener != null ? listener : ExecuteListeners.get(ctx)); this.cursorFields = fields; diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java index d46f3d151f..9d66a2f81b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java @@ -38,26 +38,10 @@ package org.jooq.impl; -import static java.lang.Math.max; -import static java.lang.Math.min; -import static org.jooq.XMLFormat.RecordFormat.COLUMN_NAME_ELEMENTS; -import static org.jooq.XMLFormat.RecordFormat.VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE; -import static org.jooq.impl.DSL.insertInto; -import static org.jooq.impl.DSL.name; -import static org.jooq.impl.DSL.table; import static org.jooq.impl.Tools.indexOrFail; -import static org.jooq.tools.StringUtils.abbreviate; -import static org.jooq.tools.StringUtils.leftPad; -import static org.jooq.tools.StringUtils.rightPad; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.StringWriter; -import java.io.Writer; import java.lang.reflect.Array; -import java.sql.Date; import java.sql.ResultSet; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -70,25 +54,12 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; -import java.util.TreeMap; -import javax.xml.bind.DatatypeConverter; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.jooq.CSVFormat; -import org.jooq.ChartFormat; -import org.jooq.ChartFormat.Display; import org.jooq.Configuration; -import org.jooq.Constants; import org.jooq.Converter; -import org.jooq.DSLContext; import org.jooq.DataType; -import org.jooq.EnumType; import org.jooq.Field; import org.jooq.ForeignKey; -import org.jooq.JSONFormat; import org.jooq.Name; import org.jooq.Record; import org.jooq.Record1; @@ -118,41 +89,26 @@ import org.jooq.RecordMapper; import org.jooq.RecordType; import org.jooq.Result; import org.jooq.Row; -import org.jooq.Schema; import org.jooq.TXTFormat; import org.jooq.Table; -import org.jooq.TableField; import org.jooq.TableRecord; import org.jooq.UpdatableRecord; -import org.jooq.XMLFormat; -import org.jooq.exception.IOException; import org.jooq.exception.InvalidResultException; import org.jooq.tools.Convert; -import org.jooq.tools.StringUtils; import org.jooq.tools.jdbc.MockResultSet; -import org.jooq.tools.json.JSONValue; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; /** * @author Lukas Eder * @author Ivan Dugic */ @SuppressWarnings({"rawtypes", "unchecked" }) -final class ResultImpl implements Result { +final class ResultImpl extends AbstractCursor implements Result { /** * Generated UID */ private static final long serialVersionUID = 6416154375799578362L; - private Configuration configuration; - private final Fields fields; private final List records; ResultImpl(Configuration configuration, Collection> fields) { @@ -164,8 +120,8 @@ final class ResultImpl implements Result { } ResultImpl(Configuration configuration, Fields fields) { - this.configuration = configuration; - this.fields = fields; + super(configuration, fields); + this.records = new ArrayList(); } @@ -393,1358 +349,6 @@ final class ResultImpl implements Result { records.add(record); } - @Override - public final String format() { - return format(TXTFormat.DEFAULT); - } - - @Override - public final String format(int maxRecords) { - return format(TXTFormat.DEFAULT.maxRows(maxRecords)); - } - - @Override - public final String format(TXTFormat format) { - StringWriter writer = new StringWriter(); - format(writer, format); - return writer.toString(); - } - - @Override - public final void format(OutputStream stream) { - format(new OutputStreamWriter(stream)); - } - - @Override - public final void format(OutputStream stream, int maxRecords) { - format(new OutputStreamWriter(stream), maxRecords); - } - - @Override - public final void format(OutputStream stream, TXTFormat format) { - format(new OutputStreamWriter(stream), format); - } - - @Override - public final void format(Writer writer) { - format(writer, TXTFormat.DEFAULT); - } - - @Override - public final void format(Writer writer, int maxRecords) { - format(writer, TXTFormat.DEFAULT.maxRows(maxRecords)); - } - - @Override - public final void format(Writer writer, TXTFormat format) { - try { - - // Numeric columns have greater max width because values are aligned - final int NUM_COL_MAX_WIDTH = format.maxColWidth() == Integer.MAX_VALUE ? Integer.MAX_VALUE : 2 * format.maxColWidth(); - - // The max number of records that will be considered for formatting purposes - final int MAX_RECORDS = min(50, format.maxRows()); - - // Get max decimal places for numeric type columns - final int[] decimalPlaces = new int[fields.fields.length]; - final int[] widths = new int[fields.fields.length]; - - for (int index = 0; index < fields.fields.length; index++) { - if (Number.class.isAssignableFrom(fields.fields[index].getType())) { - List decimalPlacesList = new ArrayList(); - - // Initialize - decimalPlacesList.add(0); - - // Collect all decimal places for the column values - String value; - for (int i = 0; i < min(MAX_RECORDS, size()); i++) { - value = format0(getValue(i, index), get(i).changed(index), true); - decimalPlacesList.add(getDecimalPlaces(value)); - } - - // Find max - decimalPlaces[index] = Collections.max(decimalPlacesList); - } - } - - // Get max column widths - int colMaxWidth; - for (int index = 0; index < fields.fields.length; index++) { - - // Is number column? - boolean isNumCol = Number.class.isAssignableFrom(fields.fields[index].getType()); - - colMaxWidth = isNumCol ? NUM_COL_MAX_WIDTH : format.maxColWidth(); - - // Collect all widths for the column - List widthList = new ArrayList(); - - // Add column name width first - widthList.add(min(colMaxWidth, max(format.minColWidth(), fields.fields[index].getName().length()))); - - // Add column values width - String value; - for (int i = 0; i < min(MAX_RECORDS, size()); i++) { - value = format0(getValue(i, index), get(i).changed(index), true); - // Align number values before width is calculated - if (isNumCol) { - value = alignNumberValue(decimalPlaces[index], value); - } - - widthList.add(min(colMaxWidth, value.length())); - } - - // Find max - widths[index] = Collections.max(widthList); - } - - // Begin the writing - // --------------------------------------------------------------------- - - // Write top line - if (format.horizontalTableBorder()) - formatHorizontalLine(writer, format, widths); - - // Write headers - if (format.verticalTableBorder()) - writer.append('|'); - - for (int index = 0; index < fields.fields.length; index++) { - if (index > 0) - if (format.verticalCellBorder()) - writer.append('|'); - else - writer.append(' '); - - String padded; - - if (Number.class.isAssignableFrom(fields.fields[index].getType())) - padded = leftPad(fields.fields[index].getName(), widths[index]); - else - padded = rightPad(fields.fields[index].getName(), widths[index]); - - writer.append(abbreviate(padded, widths[index])); - } - - if (format.verticalTableBorder()) - writer.append('|'); - - writer.append('\n'); - - // Write separator - if (format.horizontalHeaderBorder()) - formatHorizontalLine(writer, format, widths); - - // Write columns - for (int i = 0; i < min(format.maxRows(), size()); i++) { - - // Write separator - if (i > 0 && format.horizontalCellBorder()) - formatHorizontalLine(writer, format, widths); - - if (format.verticalTableBorder()) - writer.append('|'); - - for (int index = 0; index < fields.fields.length; index++) { - if (index > 0) - if (format.verticalCellBorder()) - writer.append('|'); - else - writer.append(' '); - - String value = - StringUtils.replace( - StringUtils.replace( - StringUtils.replace( - format0(getValue(i, index), get(i).changed(index), true), "\n", "{lf}" - ), "\r", "{cr}" - ), "\t", "{tab}" - ); - - String padded; - if (Number.class.isAssignableFrom(fields.fields[index].getType())) { - // Align number value before left pad - value = alignNumberValue(decimalPlaces[index], value); - - // Left pad - padded = leftPad(value, widths[index]); - } - else { - // Right pad - padded = rightPad(value, widths[index]); - } - - writer.append(abbreviate(padded, widths[index])); - } - - if (format.verticalTableBorder()) - writer.append('|'); - - writer.append('\n'); - } - - // Write bottom line - if (format.horizontalTableBorder() && size() > 0) - formatHorizontalLine(writer, format, widths); - - // Write truncation message, if applicable - if (format.maxRows() < size()) { - if (format.verticalTableBorder()) - writer.append('|'); - - writer.append("..."); - writer.append("" + (size() - format.maxRows())); - writer.append(" record(s) truncated...\n"); - } - - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing TEXT", e); - } - } - - private final void formatHorizontalLine(Writer writer, TXTFormat format, final int[] widths) throws java.io.IOException { - if (format.verticalTableBorder()) - if (format.intersectLines()) - writer.append('+'); - else - writer.append('-'); - - for (int index = 0; index < fields.fields.length; index++) { - if (index > 0) - if (format.verticalCellBorder()) - if (format.intersectLines()) - writer.append('+'); - else - writer.append('-'); - else - writer.append(' '); - - writer.append(rightPad("", widths[index], "-")); - } - - if (format.verticalTableBorder()) - if (format.intersectLines()) - writer.append('+'); - else - writer.append('-'); - - writer.append('\n'); - } - - private static final String alignNumberValue(Integer columnDecimalPlaces, String value) { - if (!"{null}".equals(value) && columnDecimalPlaces != 0) { - int decimalPlaces = getDecimalPlaces(value); - int rightPadSize = value.length() + columnDecimalPlaces - decimalPlaces; - - if (decimalPlaces == 0) { - // If integer value, add one for decimal point - value = rightPad(value, rightPadSize + 1); - } - else { - value = rightPad(value, rightPadSize); - } - } - - return value; - } - - private static final int getDecimalPlaces(String value) { - int decimalPlaces = 0; - - int dotIndex = value.indexOf("."); - if (dotIndex != -1) { - decimalPlaces = value.length() - dotIndex - 1; - } - - return decimalPlaces; - } - - @Override - public final String formatHTML() { - StringWriter writer = new StringWriter(); - formatHTML(writer); - return writer.toString(); - } - - @Override - public final void formatHTML(OutputStream stream) { - formatHTML(new OutputStreamWriter(stream)); - } - - @Override - public final void formatHTML(Writer writer) { - try { - writer.append(""); - writer.append(""); - writer.append(""); - - for (Field field : fields.fields) { - writer.append(""); - } - - writer.append(""); - writer.append(""); - writer.append(""); - - for (Record record : this) { - writer.append(""); - - for (int index = 0; index < fields.fields.length; index++) { - writer.append(""); - } - - writer.append(""); - } - - writer.append(""); - writer.append("
"); - writer.append(escapeXML(field.getName())); - writer.append("
"); - writer.append(escapeXML(format0(record.getValue(index), false, true))); - writer.append("
"); - - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing HTML", e); - } - } - - @Override - public final String formatCSV() { - return formatCSV(true); - } - - @Override - public final String formatCSV(boolean header) { - StringWriter writer = new StringWriter(); - formatCSV(writer, header); - return writer.toString(); - } - - @Override - public final void formatCSV(OutputStream stream) { - formatCSV(stream, true); - } - - @Override - public final void formatCSV(OutputStream stream, boolean header) { - formatCSV(new OutputStreamWriter(stream), header); - } - - @Override - public final void formatCSV(Writer writer) { - formatCSV(writer, true); - } - - @Override - public final void formatCSV(Writer writer, boolean header) { - formatCSV(writer, header, ',', "\"\""); - } - - @Override - public final String formatCSV(char delimiter) { - return formatCSV(true, delimiter); - } - - @Override - public final String formatCSV(boolean header, char delimiter) { - StringWriter writer = new StringWriter(); - formatCSV(writer, delimiter); - return writer.toString(); - } - - @Override - public final void formatCSV(OutputStream stream, char delimiter) { - formatCSV(stream, true, delimiter); - } - - @Override - public final void formatCSV(OutputStream stream, boolean header, char delimiter) { - formatCSV(new OutputStreamWriter(stream), delimiter); - } - - @Override - public final void formatCSV(Writer writer, char delimiter) { - formatCSV(writer, true, delimiter); - } - - @Override - public final void formatCSV(Writer writer, boolean header, char delimiter) { - formatCSV(writer, header, delimiter, "\"\""); - } - - @Override - public final String formatCSV(char delimiter, String nullString) { - return formatCSV(true, delimiter, nullString); - } - - @Override - public final String formatCSV(boolean header, char delimiter, String nullString) { - StringWriter writer = new StringWriter(); - formatCSV(writer, header, delimiter, nullString); - return writer.toString(); - } - - @Override - public final String formatCSV(CSVFormat format) { - StringWriter writer = new StringWriter(); - formatCSV(writer, format); - return writer.toString(); - } - - @Override - public final void formatCSV(OutputStream stream, char delimiter, String nullString) { - formatCSV(stream, true, delimiter, nullString); - } - - @Override - public final void formatCSV(OutputStream stream, boolean header, char delimiter, String nullString) { - formatCSV(new OutputStreamWriter(stream), header, delimiter, nullString); - } - - @Override - public final void formatCSV(OutputStream stream, CSVFormat format) { - formatCSV(new OutputStreamWriter(stream), format); - } - - @Override - public final void formatCSV(Writer writer, char delimiter, String nullString) { - formatCSV(writer, true, delimiter, nullString); - } - - @Override - public final void formatCSV(Writer writer, boolean header, char delimiter, String nullString) { - formatCSV(writer, new CSVFormat().header(header).delimiter(delimiter).nullString(nullString)); - } - - @Override - public final void formatCSV(Writer writer, CSVFormat format) { - try { - if (format.header()) { - String sep1 = ""; - for (Field field : fields.fields) { - writer.append(sep1); - writer.append(formatCSV0(field.getName(), format)); - - sep1 = format.delimiter(); - } - - writer.append(format.newline()); - } - - for (Record record : this) { - String sep2 = ""; - - for (int index = 0; index < fields.fields.length; index++) { - writer.append(sep2); - writer.append(formatCSV0(record.getValue(index), format)); - - sep2 = format.delimiter(); - } - - writer.append(format.newline()); - } - - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing CSV", e); - } - } - - private final String formatCSV0(Object value, CSVFormat format) { - - // [#2741] TODO: This logic will be externalised in new SPI - // [#4746] Escape null and empty strings - if (value == null) - return format.nullString(); - - if ("".equals(value.toString())) - return format.emptyString(); - - String result = format0(value, false, false); - switch (format.quote()) { - case NEVER: - return result; - - case SPECIAL_CHARACTERS: - if (!StringUtils.containsAny(result, ',', ';', '\t', '"', '\n', '\r', '\'', '\\')) - return result; - - // no break - case ALWAYS: - default: - return format.quoteString() - + StringUtils.replace( - StringUtils.replace( - result, "\\", "\\\\" - ), format.quoteString(), format.quoteString() + format.quoteString() - ) - + format.quoteString(); - } - } - - private static final void formatJSON0(Object value, Writer writer) throws java.io.IOException { - - // [#2741] TODO: This logic will be externalised in new SPI - if (value instanceof byte[]) { - JSONValue.writeJSONString(DatatypeConverter.printBase64Binary((byte[]) value), writer); - } - - // [#6563] Arrays can be serialised natively in JSON - else if (value instanceof Object[]) { - Object[] array = (Object[]) value; - writer.append('['); - - for (int i = 0; i < array.length; i++) { - if (i > 0) - writer.append(','); - - formatJSON0(array[i], writer); - } - - writer.append(']'); - } - - else { - JSONValue.writeJSONString(value, writer); - } - } - - /** - * @param value The value to be formatted - * @param visual Whether the formatted output is to be consumed visually - * (HTML, TEXT) or by a machine (CSV, JSON, XML) - */ - private static final String format0(Object value, boolean changed, boolean visual) { - - // [#2741] TODO: This logic will be externalised in new SPI - String formatted = changed && visual ? "*" : ""; - - if (value == null) { - formatted += visual ? "{null}" : null; - } - else if (value.getClass() == byte[].class) { - formatted += DatatypeConverter.printBase64Binary((byte[]) value); - } - else if (value.getClass().isArray()) { - // [#6545] Nested arrays - if (value.getClass().getComponentType().isArray()) - formatted += Arrays.deepToString((Object[]) value); - else - formatted += Arrays.toString((Object[]) value); - } - else if (value instanceof EnumType) { - formatted += ((EnumType) value).getLiteral(); - } - else if (value instanceof Record) { - formatted += ((Record) value).valuesRow().toString(); - } - - // [#5238] Oracle DATE is really a TIMESTAMP(0)... - else if (value instanceof Date) { - String date = value.toString(); - - if (Date.valueOf(date).equals(value)) - formatted += date; - else - formatted += new Timestamp(((Date) value).getTime()); - } - else { - formatted += value.toString(); - } - - return formatted; - } - - @Override - public final String formatJSON() { - StringWriter writer = new StringWriter(); - formatJSON(writer); - return writer.toString(); - } - - @Override - public final String formatJSON(JSONFormat format) { - StringWriter writer = new StringWriter(); - formatJSON(writer, format); - return writer.toString(); - } - - @Override - public final void formatJSON(OutputStream stream) { - formatJSON(new OutputStreamWriter(stream)); - } - - @Override - public final void formatJSON(OutputStream stream, JSONFormat format) { - formatJSON(new OutputStreamWriter(stream), format); - } - - @Override - public final void formatJSON(Writer writer) { - formatJSON(writer, JSONFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final void formatJSON(Writer writer, JSONFormat format) { - try { - String separator; - int recordLevel = format.header() ? 2 : 1; - - if (format.header()) { - if (format.format()) - writer.append('{').append(format.newline()) - .append(format.indentString(1)).append("\"fields\": ["); - else - writer.append("{\"fields\":["); - - separator = ""; - - for (Field field : fields.fields) { - writer.append(separator); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(2)); - - writer.append('{'); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(3)); - - if (field instanceof TableField) { - Table table = ((TableField) field).getTable(); - - if (table != null) { - Schema schema = table.getSchema(); - - if (schema != null) { - writer.append("\"schema\":"); - - if (format.format()) - writer.append(' '); - - JSONValue.writeJSONString(schema.getName(), writer); - writer.append(','); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(3)); - } - - writer.append("\"table\":"); - - if (format.format()) - writer.append(' '); - - JSONValue.writeJSONString(table.getName(), writer); - writer.append(','); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(3)); - } - } - - writer.append("\"name\":"); - - if (format.format()) - writer.append(' '); - - JSONValue.writeJSONString(field.getName(), writer); - writer.append(','); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(3)); - - writer.append("\"type\":"); - - if (format.format()) - writer.append(' '); - - JSONValue.writeJSONString(field.getDataType().getTypeName().toUpperCase(), writer); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(2)); - - writer.append('}'); - separator = ","; - } - - if (format.format()) - writer.append(format.newline()).append(format.indentString(1)).append("],") - .append(format.newline()).append(format.indentString(1)).append("\"records\": "); - else - writer.append("],\"records\":"); - } - - writer.append('['); - separator = ""; - - switch (format.recordFormat()) { - case ARRAY: - for (Record record : this) { - writer.append(separator); - - if (format.format()) - writer.append(format.newline()); - - formatJSONArray0(record, fields, format, recordLevel, writer); - separator = ","; - } - - break; - case OBJECT: - for (Record record : this) { - writer.append(separator); - - if (format.format()) - writer.append(format.newline()); - - formatJSONMap0(record, fields, format, recordLevel, writer); - separator = ","; - } - - break; - default: - throw new IllegalArgumentException("Format not supported: " + format); - } - - if (format.format()) { - writer.append(format.newline()); - - if (format.header()) - writer.append(format.indentString(1)); - } - - writer.append(']'); - - if (format.header()) - writer.append(format.newline()).append('}'); - - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing JSON", e); - } - } - - static final void formatJSONMap0(Record record, Fields fields, JSONFormat format, int recordLevel, Writer writer) throws java.io.IOException { - String separator = ""; - - if (format.format()) - writer.append(format.indentString(recordLevel)).append('{'); - else - writer.append('{'); - - for (int index = 0; index < fields.fields.length; index++) { - writer.append(separator); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel + 1)); - - JSONValue.writeJSONString(record.field(index).getName(), writer); - writer.append(':'); - if (format.format()) - writer.append(' '); - - formatJSON0(record.get(index), writer); - separator = ","; - } - - if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel)); - - writer.append('}'); - } - - static final void formatJSONArray0(Record record, Fields fields, JSONFormat format, int recordLevel, Writer writer) throws java.io.IOException { - String separator = ""; - - if (format.format()) - writer.append(format.indentString(recordLevel)).append('['); - else - writer.append('['); - - for (int index = 0; index < fields.fields.length; index++) { - writer.append(separator); - - if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel + 1)); - - formatJSON0(record.get(index), writer); - separator = ","; - } - - if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel)); - - writer.append(']'); - } - - @Override - public final String formatXML() { - return formatXML(XMLFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final String formatXML(XMLFormat format) { - StringWriter writer = new StringWriter(); - formatXML(writer, format); - return writer.toString(); - } - - @Override - public final void formatXML(OutputStream stream) { - formatXML(stream, XMLFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final void formatXML(OutputStream stream, XMLFormat format) { - formatXML(new OutputStreamWriter(stream), format); - } - - @Override - public final void formatXML(Writer writer) { - formatXML(writer, XMLFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final void formatXML(Writer writer, XMLFormat format) { - String newline = format.newline(); - int recordLevel = format.header() ? 2 : 1; - - try { - writer.append(""); - - if (format.header()) { - writer.append(newline).append(format.indentString(1)).append(""); - - for (Field field : fields.fields) { - writer.append(newline).append(format.indentString(2)).append(" table = ((TableField) field).getTable(); - - if (table != null) { - Schema schema = table.getSchema(); - - if (schema != null) { - writer.append(" schema=\""); - writer.append(escapeXML(schema.getName())); - writer.append("\""); - } - - writer.append(" table=\""); - writer.append(escapeXML(table.getName())); - writer.append("\""); - } - } - - writer.append(" name=\""); - writer.append(escapeXML(field.getName())); - writer.append("\""); - writer.append(" type=\""); - writer.append(field.getDataType().getTypeName().toUpperCase()); - writer.append("\"/>"); - } - - writer.append(newline).append(format.indentString(1)).append(""); - writer.append(newline).append(format.indentString(1)).append(""); - } - - for (Record record : this) { - writer.append(newline).append(format.indentString(recordLevel)); - formatXMLRecord(writer, format, recordLevel, record, fields); - } - - if (format.header()) - writer.append(newline).append(format.indentString(1)).append(""); - - writer.append(newline).append(""); - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing XML", e); - } - } - - static final void formatXMLRecord( - Writer writer, - XMLFormat format, - int recordLevel, - Record record, - Fields fields - ) - throws java.io.IOException { - String newline = format.newline(); - - writer.append(""); - - for (int index = 0; index < fields.fields.length; index++) { - Object value = record.get(index); - - writer.append(newline).append(format.indentString(recordLevel + 1)); - String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS - ? escapeXML(fields.fields[index].getName()) - : "value"; - - writer.append("<" + tag); - if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) { - writer.append(" field=\""); - writer.append(escapeXML(fields.fields[index].getName())); - writer.append("\""); - } - - if (value == null) { - writer.append("/>"); - } - else { - writer.append(">"); - writer.append(escapeXML(format0(value, false, false))); - writer.append(""); - } - } - - writer.append(newline).append(format.indentString(recordLevel)).append(""); - } - - @Override - public final String formatChart() { - StringWriter writer = new StringWriter(); - formatChart(writer); - return writer.toString(); - } - - @Override - public final String formatChart(ChartFormat format) { - StringWriter writer = new StringWriter(); - formatChart(writer, format); - return writer.toString(); - } - - @Override - public final void formatChart(OutputStream stream) { - formatChart(new OutputStreamWriter(stream)); - } - - @Override - public final void formatChart(OutputStream stream, ChartFormat format) { - formatChart(new OutputStreamWriter(stream), format); - } - - @Override - public final void formatChart(Writer writer) { - formatChart(writer, ChartFormat.DEFAULT); - } - - @Override - public final void formatChart(Writer writer, ChartFormat format) { - try { - Field category = field(format.category()); - TreeMap> groups = new TreeMap>(intoGroups(format.category())); - - if (!format.categoryAsText()) { - if (Date.class.isAssignableFrom(category.getType())) { - Date categoryMin = (Date) groups.firstKey(); - Date categoryMax = (Date) groups.lastKey(); - - for (Date i = categoryMin; i.before(categoryMax); i = new Date(i.getYear(), i.getMonth(), i.getDate() + 1)) - if (!groups.containsKey(i)) - groups.put(i, (Result) DSL.using(configuration).newResult(fields())); - } - } - - List categories = new ArrayList(groups.keySet()); - - int categoryPadding = 1; - int categoryWidth = 0; - for (Object o : categories) - categoryWidth = Math.max(categoryWidth, ("" + o).length()); - - double axisMin = Double.POSITIVE_INFINITY; - double axisMax = Double.NEGATIVE_INFINITY; - - for (Result values : groups.values()) { - double sum = 0; - - for (int i = 0; i < format.values().length; i++) { - if (format.display() == Display.DEFAULT) - sum = 0; - - for (Record r : values) - sum = sum + r.get(format.values()[i], double.class); - - if (sum < axisMin) - axisMin = sum; - - if (sum > axisMax) - axisMax = sum; - } - } - - int verticalLegendWidth = format.showVerticalLegend() - ? Math.max( - format.numericFormat().format(axisMin).length(), - format.numericFormat().format(axisMax).length() - ) - : 0; - - int horizontalLegendHeight = format.showHorizontalLegend() ? 1 : 0; - - int verticalBorderWidth = format.showVerticalLegend() ? 1 : 0; - int horizontalBorderHeight = format.showHorizontalLegend() ? 1 : 0; - - int chartHeight = format.height() - horizontalLegendHeight - horizontalBorderHeight; - int chartWidth = format.width() - verticalLegendWidth - verticalBorderWidth; - - double barWidth = (double) chartWidth / groups.size(); - double axisStep = (axisMax - axisMin) / (chartHeight - 1); - - for (int y = chartHeight - 1; y >= 0; y--) { - double axisLegend = axisMax - (axisStep * (chartHeight - 1 - y)); - double axisLegendPercent = (axisLegend - axisMin) / (axisMax - axisMin); - - if (format.showVerticalLegend()) { - String axisLegendString = (format.display() == Display.HUNDRED_PERCENT_STACKED) - ? format.numericFormat().format(axisLegendPercent * 100.0) + "%" - : format.numericFormat().format(axisLegend); - - for (int x = axisLegendString.length(); x < verticalLegendWidth; x++) - writer.write(' '); - - writer.write(axisLegendString); - - for (int x = 0; x < verticalBorderWidth; x++) - writer.write('|'); - } - - for (int x = 0; x < chartWidth; x++) { - int index = (int) (x / barWidth); - - Result group = groups.get(categories.get(index)); - double[] values = new double[format.values().length]; - - for (Record record : group) - for (int i = 0; i < values.length; i++) - values[i] = values[i] + record.get(format.values()[i], double.class); - - if (format.display() == Display.STACKED || format.display() == Display.HUNDRED_PERCENT_STACKED) - for (int i = 1; i < values.length; i++) - values[i] = values[i] + values[i - 1]; - - if (format.display() == Display.HUNDRED_PERCENT_STACKED) - for (int i = 0; i < values.length; i++) - values[i] = values[i] / values[values.length - 1]; - - int shadeIndex = -1; - for (int i = values.length - 1; i >= 0; i--) - if ((format.display() == Display.HUNDRED_PERCENT_STACKED ? axisLegendPercent : axisLegend) > values[i]) - break; - else - shadeIndex = i; - - if (shadeIndex == -1) - writer.write(' '); - else - writer.write(format.shades()[shadeIndex % format.shades().length]); - } - - writer.write(format.newline()); - } - - if (format.showHorizontalLegend()) { - for (int y = 0; y < horizontalBorderHeight; y++) { - if (format.showVerticalLegend()) { - for (int x = 0; x < verticalLegendWidth; x++) - writer.write('-'); - - for (int x = 0; x < verticalBorderWidth; x++) - writer.write('+'); - } - - for (int x = 0; x < chartWidth; x++) - writer.write('-'); - - writer.write(format.newline()); - } - - for (int y = 0; y < horizontalLegendHeight; y++) { - if (format.showVerticalLegend()) { - for (int x = 0; x < verticalLegendWidth; x++) - writer.write(' '); - - for (int x = 0; x < verticalBorderWidth; x++) - writer.write('|'); - } - - double rounding = 0.0; - for (double x = 0.0; x < chartWidth;) { - String label = "" + categories.get((int) (x / barWidth)); - int length = label.length(); - - double padding = Math.max(categoryPadding, (barWidth - length) / 2); - - rounding = (rounding + padding - Math.floor(padding)) % 1; - x = x + (padding + rounding); - for (int i = 0; i < (int) (padding + rounding); i++) - writer.write(' '); - - x = x + length; - if (x >= chartWidth) - break; - writer.write(label); - - rounding = (rounding + padding - Math.floor(padding)) % 1; - x = x + (padding + rounding); - for (int i = 0; i < (int) (padding + rounding); i++) - writer.write(' '); - } - - writer.write(format.newline()); - } - } - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing Chart", e); - } - } - - @Override - public final String formatInsert() { - StringWriter writer = new StringWriter(); - formatInsert(writer); - return writer.toString(); - } - - @Override - public final void formatInsert(OutputStream stream) { - formatInsert(new OutputStreamWriter(stream)); - } - - @Override - public final void formatInsert(Writer writer) { - Table table = null; - - if (records.size() > 0 && records.get(0) instanceof TableRecord) - table = ((TableRecord) records.get(0)).getTable(); - - if (table == null) - table = table(name("UNKNOWN_TABLE")); - - formatInsert(writer, table, fields()); - } - - @Override - public final String formatInsert(Table table, Field... f) { - StringWriter writer = new StringWriter(); - formatInsert(writer, table, f); - return writer.toString(); - } - - @Override - public final void formatInsert(OutputStream stream, Table table, Field... f) { - formatInsert(new OutputStreamWriter(stream), table, f); - } - - @Override - public final void formatInsert(Writer writer, Table table, Field... f) { - DSLContext ctx = DSL.using(configuration()); - - try { - for (R record : this) { - writer.append(ctx.renderInlined(insertInto(table, f).values(record.intoArray()))); - writer.append(";\n"); - } - - writer.flush(); - } - catch (java.io.IOException e) { - throw new IOException("Exception while writing INSERTs", e); - } - } - - @Override - public final Document intoXML() { - return intoXML(XMLFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final Document intoXML(XMLFormat format) { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document document = builder.newDocument(); - - Element eResult = document.createElement("result"); - - if (format.xmlns()) - eResult.setAttribute("xmlns", Constants.NS_EXPORT); - document.appendChild(eResult); - - Element eRecordParent = eResult; - - if (format.header()) { - Element eFields = document.createElement("fields"); - eResult.appendChild(eFields); - - for (Field field : fields.fields) { - Element eField = document.createElement("field"); - - if (field instanceof TableField) { - Table table = ((TableField) field).getTable(); - - if (table != null) { - Schema schema = table.getSchema(); - - if (schema != null) { - eField.setAttribute("schema", schema.getName()); - } - - eField.setAttribute("table", table.getName()); - } - } - - eField.setAttribute("name", field.getName()); - eField.setAttribute("type", field.getDataType().getTypeName().toUpperCase()); - eFields.appendChild(eField); - } - - Element eRecords = document.createElement("records"); - eResult.appendChild(eRecords); - eRecordParent = eRecords; - } - - for (Record record : this) { - Element eRecord = document.createElement("record"); - eRecordParent.appendChild(eRecord); - - for (int index = 0; index < fields.fields.length; index++) { - Field field = fields.fields[index]; - Object value = record.get(index); - - String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS - ? escapeXML(fields.fields[index].getName()) - : "value"; - - Element eValue = document.createElement(tag); - - if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) - eValue.setAttribute("field", field.getName()); - eRecord.appendChild(eValue); - - if (value != null) { - eValue.setTextContent(format0(value, false, false)); - } - } - } - - return document; - } - catch (ParserConfigurationException ignore) { - throw new RuntimeException(ignore); - } - } - - @Override - public final H intoXML(H handler) throws SAXException { - return intoXML(handler, XMLFormat.DEFAULT_FOR_RESULTS); - } - - @Override - public final H intoXML(H handler, XMLFormat format) throws SAXException { - Attributes empty = new AttributesImpl(); - - handler.startDocument(); - - if (format.xmlns()) - handler.startPrefixMapping("", Constants.NS_EXPORT); - - handler.startElement("", "", "result", empty); - if (format.header()) { - handler.startElement("", "", "fields", empty); - - for (Field field : fields.fields) { - AttributesImpl attrs = new AttributesImpl(); - - if (field instanceof TableField) { - Table table = ((TableField) field).getTable(); - - if (table != null) { - Schema schema = table.getSchema(); - - if (schema != null) { - attrs.addAttribute("", "", "schema", "CDATA", schema.getName()); - } - - attrs.addAttribute("", "", "table", "CDATA", table.getName()); - } - } - - attrs.addAttribute("", "", "name", "CDATA", field.getName()); - attrs.addAttribute("", "", "type", "CDATA", field.getDataType().getTypeName().toUpperCase()); - - handler.startElement("", "", "field", attrs); - handler.endElement("", "", "field"); - } - - handler.endElement("", "", "fields"); - handler.startElement("", "", "records", empty); - } - - for (Record record : this) { - handler.startElement("", "", "record", empty); - - for (int index = 0; index < fields.fields.length; index++) { - Field field = fields.fields[index]; - Object value = record.get(index); - - String tag = format.recordFormat() == COLUMN_NAME_ELEMENTS - ? escapeXML(fields.fields[index].getName()) - : "value"; - - AttributesImpl attrs = new AttributesImpl(); - - if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) - attrs.addAttribute("", "", "field", "CDATA", field.getName()); - - handler.startElement("", "", tag, attrs); - - if (value != null) { - char[] chars = format0(value, false, false).toCharArray(); - handler.characters(chars, 0, chars.length); - } - - handler.endElement("", "", tag); - } - - handler.endElement("", "", "record"); - } - - if (format.header()) - handler.endElement("", "", "records"); - - if (format.xmlns()) - handler.endPrefixMapping(""); - - handler.endDocument(); - return handler; - } - - private static final String escapeXML(String string) { - return StringUtils.replaceEach(string, - new String[] { "\"", "'", "<", ">", "&" }, - new String[] { """, "'", "<", ">", "&"}); - } - @Override public final List> intoMaps() { List> list = new ArrayList>();