/*
*
* SchemaCrawler
* http://sourceforge.net/projects/schemacrawler
* Copyright (c) 2000-2011, Sualeh Fatehi.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
package schemacrawler.tools.text.operation;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.hsqldb.types.Types;
import schemacrawler.crawl.SchemaCrawler;
import schemacrawler.schema.ResultsColumn;
import schemacrawler.schemacrawler.SchemaCrawlerException;
import schemacrawler.tools.options.OutputOptions;
import schemacrawler.tools.text.base.BaseFormatter;
import schemacrawler.tools.text.util.TextFormattingHelper.DocumentHeaderType;
import schemacrawler.utility.SchemaCrawlerUtility;
/**
* Text formatting of data.
*
* @author Sualeh Fatehi
*/
final class DataTextFormatter
extends BaseFormatter<OperationOptions>
{
private static final Logger LOGGER = Logger.getLogger(DataTextFormatter.class
.getName());
private static final String NULL = "<null>";
private static final String BINARY = "<binary>";
private int dataBlockCount;
private final Operation operation;
/**
* Text formatting of data.
*
* @param operation
* Options for text formatting of data
* @param options
* Options for text formatting of data
* @param outputOptions
* Options for text formatting of data
*/
DataTextFormatter(final Operation operation,
final OperationOptions options,
final OutputOptions outputOptions)
throws SchemaCrawlerException
{
super(options, /* printVerboseDatabaseInfo */false, outputOptions);
this.operation = operation;
}
private String convertColumnDataToString(final ResultsColumn[] resultsColumns,
final ResultSet rows,
final int i)
throws SQLException
{
final int javaSqlType = resultsColumns[i].getType().getType();
String columnDataString;
if (javaSqlType == Types.CLOB)
{
final Clob clob = rows.getClob(i + 1);
if (rows.wasNull() || clob == null)
{
columnDataString = NULL;
}
else
{
columnDataString = readClob(clob);
}
}
else if (javaSqlType == Types.NCLOB)
{
final NClob nClob = rows.getNClob(i + 1);
if (rows.wasNull() || nClob == null)
{
columnDataString = NULL;
}
else
{
columnDataString = readClob(nClob);
}
}
else if (javaSqlType == Types.BLOB)
{
final Blob blob = rows.getBlob(i + 1);
if (rows.wasNull() || blob == null)
{
columnDataString = NULL;
}
else
{
columnDataString = readBlob(blob);
}
}
else if (javaSqlType == Types.LONGVARBINARY)
{
final InputStream stream = rows.getBinaryStream(i + 1);
if (rows.wasNull() || stream == null)
{
columnDataString = NULL;
}
else
{
columnDataString = readStream(stream);
}
}
else if (javaSqlType == Types.LONGNVARCHAR
|| javaSqlType == Types.LONGVARCHAR)
{
final InputStream stream = rows.getAsciiStream(i + 1);
if (rows.wasNull() || stream == null)
{
columnDataString = NULL;
}
else
{
columnDataString = readStream(stream);
}
}
else
{
final Object columnData = rows.getObject(i + 1);
if (rows.wasNull() || columnData == null)
{
columnDataString = NULL;
}
else
{
columnDataString = columnData.toString();
}
}
return columnDataString;
}
private void doHandleOneRow(final List<String> row,
final String lastColumnData)
{
if (row.isEmpty())
{
return;
}
final List<String> outputRow = new ArrayList<String>();
// output
outputRow.addAll(row);
outputRow.add(lastColumnData);
final String[] columnData = outputRow.toArray(new String[outputRow.size()]);
out.println(formattingHelper.createRow(columnData));
}
private String getMessage(final double aggregate)
{
final Number number;
if (Math.abs(aggregate - (int) aggregate) < 1E-10D)
{
number = Integer.valueOf((int) aggregate);
}
else
{
number = Double.valueOf(aggregate);
}
final String message = operation.getCountMessage(number);
return message;
}
/**
* Handles an aggregate operation, such as a count, for a given table.
*
* @param title
* Title
* @param results
* Results
*/
private void handleAggregateOperationForTable(final String title,
final ResultSet results)
throws SchemaCrawlerException
{
long aggregate = 0;
try
{
if (results.next())
{
aggregate = results.getLong(1);
}
}
catch (final SQLException e)
{
throw new SchemaCrawlerException("Could not obtain aggregate data", e);
}
final String message = getMessage(aggregate);
//
out.println(formattingHelper.createNameRow(title, message, false));
}
private void iterateRows(final ResultsColumn[] resultsColumns,
final ResultSet rows)
throws SQLException
{
List<String> currentRow;
while (rows.next())
{
final int columnCount = resultsColumns.length;
currentRow = new ArrayList<String>(columnCount);
for (int i = 0; i < columnCount; i++)
{
final String columnDataString = convertColumnDataToString(resultsColumns,
rows,
i);
currentRow.add(columnDataString);
}
final String[] columnData = currentRow.toArray(new String[currentRow
.size()]);
out.println(formattingHelper.createRow(columnData));
}
}
private void iterateRowsAndMerge(final ResultsColumn[] resultsColumns,
final ResultSet rows)
throws SQLException
{
final int columnCount = resultsColumns.length;
List<String> previousRow = new ArrayList<String>();
List<String> currentRow;
StringBuilder currentRowLastColumn = new StringBuilder();
// write out the data
while (rows.next())
{
currentRow = new ArrayList<String>(columnCount - 1);
for (int i = 0; i < columnCount - 1; i++)
{
final String columnDataString = convertColumnDataToString(resultsColumns,
rows,
i);
currentRow.add(columnDataString);
}
final String lastColumnDataString = convertColumnDataToString(resultsColumns,
rows,
resultsColumns.length - 1);
if (currentRow.equals(previousRow))
{
currentRowLastColumn.append(lastColumnDataString);
}
else
{
// At this point, we have a new row coming in, so dump the
// previous merged row out
doHandleOneRow(previousRow, currentRowLastColumn.toString());
// reset
currentRowLastColumn = new StringBuilder();
// save the last column
currentRowLastColumn.append(lastColumnDataString);
}
previousRow = currentRow;
}
// Dump the last row out
doHandleOneRow(previousRow, currentRowLastColumn.toString());
}
private void printHeader()
{
if (operation != null)
{
out.println(formattingHelper.createHeader(DocumentHeaderType.subTitle,
operation.getDescription()));
}
else
{
out.println(formattingHelper.createHeader(DocumentHeaderType.subTitle,
"Query"));
}
if (operation == Operation.count)
{
out.println(formattingHelper
.createObjectStart(operation.getDescription()));
}
}
private String readBlob(final Blob blob)
{
if (blob == null)
{
return NULL;
}
else if (options.isShowLobs())
{
InputStream in = null;
String lobData;
try
{
try
{
in = blob.getBinaryStream();
}
catch (final SQLFeatureNotSupportedException e)
{
in = null;
}
if (in != null)
{
lobData = sf.util.Utility.readFully(in);
}
else
{
lobData = BINARY;
}
}
catch (final SQLException e)
{
LOGGER.log(Level.WARNING, "Could not read BLOB data", e);
lobData = BINARY;
}
return lobData;
}
else
{
return BINARY;
}
}
private String readClob(final Clob clob)
{
if (clob == null)
{
return NULL;
}
else if (options.isShowLobs())
{
Reader rdr = null;
String lobData;
try
{
try
{
rdr = new InputStreamReader(clob.getAsciiStream());
}
catch (final SQLFeatureNotSupportedException e)
{
rdr = null;
}
if (rdr == null)
{
try
{
rdr = clob.getCharacterStream();
}
catch (final SQLFeatureNotSupportedException e)
{
rdr = null;
}
}
if (rdr != null)
{
lobData = sf.util.Utility.readFully(rdr);
if (lobData.length() == 0)
{
// Attempt yet another read
final long clobLength = clob.length();
lobData = clob.getSubString(1, (int) clobLength);
}
}
else
{
lobData = BINARY;
}
}
catch (final SQLException e)
{
LOGGER.log(Level.WARNING, "Could not read CLOB data", e);
lobData = BINARY;
}
return lobData;
}
else
{
return BINARY;
}
}
/**
* Reads data from an input stream into a string. Default system
* encoding is assumed.
*
* @param columnData
* Column data object returned by JDBC
* @return A string with the contents of the LOB
*/
private String readStream(final InputStream stream)
{
if (stream == null)
{
return NULL;
}
else if (options.isShowLobs())
{
final BufferedInputStream in = new BufferedInputStream(stream);
final String lobData = sf.util.Utility.readFully(in);
return lobData;
}
else
{
return BINARY;
}
}
void begin()
{
if (!outputOptions.isNoHeader())
{
out.println(formattingHelper.createDocumentStart());
}
}
void end()
{
if (operation == Operation.count)
{
out.println(formattingHelper.createObjectEnd());
}
if (!outputOptions.isNoFooter())
{
out.println(formattingHelper.createDocumentEnd());
}
out.flush();
//
outputOptions.closeOutputWriter(out);
}
void handleData(final String title, final ResultSet rows)
throws SchemaCrawlerException
{
if (dataBlockCount == 0)
{
printHeader();
}
if (operation == Operation.count)
{
handleAggregateOperationForTable(title, rows);
}
else
{
out.println(formattingHelper.createObjectStart(title));
try
{
final ResultsColumn[] resultsColumns = SchemaCrawlerUtility.getResultColumns(rows).getColumns();
final int columnCount = resultsColumns.length;
final String[] columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++)
{
columnNames[i] = resultsColumns[i].getName();
}
out.println(formattingHelper.createRowHeader(columnNames));
if (options.isMergeRows() && columnCount > 1)
{
iterateRowsAndMerge(resultsColumns, rows);
}
else
{
iterateRows(resultsColumns, rows);
}
}
catch (final SQLException e)
{
throw new SchemaCrawlerException(e.getMessage(), e);
}
out.println(formattingHelper.createObjectEnd());
}
dataBlockCount++;
}
}