/**
* Copyright The Apache Software Foundation
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you 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 com.alibaba.wasp.jdbc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.TreeSet;
/**
* Format result utils.
*/
public class JdbcResultFormatter {
// private static final ResourceBundle resourceBundle = ResourceBundle
// .getBundle(JdbcResultFormatter.class.getName());
private static final Object[] EMPTY_OBJ_ARRAY = new Object[0];
// ~ Instance fields --------------------------------------------------------
private OutputFile record = null;
private StringBuilder sb = new StringBuilder();
Connection conn;
DatabaseMetaData meta;
private Opts opts = new Opts(System.getProperties());
public JdbcResultFormatter(Connection conn) throws SQLException {
this.conn = conn;
this.meta = conn.getMetaData();
}
/**
* Format sql result.
*/
public void format(Statement stmt, String sql) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
print(rs);
rs.close();
stmt.close();
// return sb.toString();
}
// /////////////////////////////////////
// ResultSet output formatting classes
// /////////////////////////////////////
public int print(ResultSet rs) throws SQLException {
OutputFormat f = new TableOutputFormat();
Rows rows = new BufferedRows(rs);
return f.print(rows);
}
/**
* Issue the specified error message
*
* @param msg
* the message to issue
*
* @return false always
*/
boolean error(String msg) {
output(color().red(msg), true, System.err);
return false;
}
boolean error(Throwable t) {
handleException(t);
return false;
}
void debug(String msg) {
if (opts.getVerbose()) {
output(color().blue(msg), true, System.err);
}
}
static String loc(String res) {
return loc(res, EMPTY_OBJ_ARRAY);
}
//
// static String loc(String res, int param) {
// try {
// return MessageFormat.format(
// new ChoiceFormat(resourceBundle.getString(res)).format(param),
// new Object[] { new Integer(param) });
// } catch (Exception e) {
// return res + ": " + param;
// }
// }
static String loc(String res, Object[] params) {
// try {
// return MessageFormat.format(resourceBundle.getString(res), params);
// } catch (Exception e) {
// e.printStackTrace();
try {
return res + ": " + Arrays.asList(params);
} catch (Exception e2) {
return res;
}
// }
}
// /////////////////////////////
// Exception handling routines
// /////////////////////////////
void handleException(Throwable e) {
while (e instanceof InvocationTargetException) {
e = ((InvocationTargetException) e).getTargetException();
}
if (e instanceof SQLException) {
handleSQLException((SQLException) e);
} else if (!(opts.getVerbose())) { // all init errors
// must be verbose
if (e.getMessage() == null) {
error(e.getClass().getName());
} else {
error(e.getMessage());
}
} else {
e.printStackTrace(System.err);
}
}
void handleSQLException(SQLException e) {
// all init errors must be verbose
if ((e instanceof SQLWarning) && !(opts.getShowWarnings())) {
return;
}
String type = (e instanceof SQLWarning) ? loc("Warning") : loc("Error");
error(loc((e instanceof SQLWarning) ? "Warning" : "Error",
new Object[] { (e.getMessage() == null) ? "" : e.getMessage().trim(),
(e.getSQLState() == null) ? "" : e.getSQLState().trim(),
new Integer(e.getErrorCode()) }));
// all init errors must be verbose
if (opts.getVerbose()) {
e.printStackTrace();
}
// all init errors must be verbose
if (!opts.getShowNestedErrs()) {
return;
}
for (SQLException nested = e.getNextException(); (nested != null)
&& (nested != e); nested = nested.getNextException()) {
handleSQLException(nested);
}
}
static String getApplicationTitle() {
Package pack = JdbcResultFormatter.class.getPackage();
return loc(
"app-introduction",
new Object[] {
"Wasp",
pack.getImplementationVersion() == null ? "???" : pack
.getImplementationVersion(), "Wasp", });
}
/**
* Entry point to creating a {@link ColorBuffer} with color enabled or
* disabled depending on the calue of {@link Opts#getColor}.
*/
ColorBuffer color() {
return new ColorBuffer(opts.getColor());
}
void output(ColorBuffer msg) {
output(msg, true);
}
void output(ColorBuffer msg, boolean newline) {
output(msg, newline, System.out);
}
void output(ColorBuffer msg, boolean newline, PrintStream out) {
if (newline) {
out.println(msg.getColor());
sb.append(msg.getColor());
sb.append("\n");
} else {
out.print(msg.getColor());
sb.append(msg.getColor());
}
if (record == null) {
return;
}
// only write to the record file if we are writing a line ...
// otherwise we might get garbage from backspaces and such.
if (newline) {
record.addLine(msg.getMono()); // always just write mono
}
}
public class OutputFile {
final File file;
final PrintWriter out;
public OutputFile(String filename) throws IOException {
file = new File(filename);
out = new PrintWriter(new FileWriter(file));
}
public String toString() {
return file.getAbsolutePath();
}
public void addLine(String command) {
out.println(command);
}
public void close() throws IOException {
out.close();
}
}
static class Reflector {
public static Object invoke(Object on, String method, Object[] args)
throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException {
return invoke(on, method, Arrays.asList(args));
}
public static Object invoke(Object on, String method, List args)
throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException {
return invoke(on, (on == null) ? null : on.getClass(), method, args);
}
public static Object invoke(Object on, Class defClass, String method,
List args) throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException {
Class c = (defClass != null) ? defClass : on.getClass();
List candidateMethods = new LinkedList();
Method[] m = c.getMethods();
for (int i = 0; i < m.length; i++) {
if (m[i].getName().equalsIgnoreCase(method)) {
candidateMethods.add(m[i]);
}
}
if (candidateMethods.size() == 0) {
throw new IllegalArgumentException(loc("no-method", new Object[] {
method, c.getName() }));
}
for (Iterator i = candidateMethods.iterator(); i.hasNext();) {
Method meth = (Method) i.next();
Class[] ptypes = meth.getParameterTypes();
if (!(ptypes.length == args.size())) {
continue;
}
Object[] converted = convert(args, ptypes);
if (converted == null) {
continue;
}
if (!Modifier.isPublic(meth.getModifiers())) {
continue;
}
return meth.invoke(on, converted);
}
return null;
}
public static Object[] convert(List objects, Class[] toTypes)
throws ClassNotFoundException {
Object[] converted = new Object[objects.size()];
for (int i = 0; i < converted.length; i++) {
converted[i] = convert(objects.get(i), toTypes[i]);
}
return converted;
}
public static Object convert(Object ob, Class toType)
throws ClassNotFoundException {
if ((ob == null) || ob.toString().equals("null")) {
return null;
}
if (toType == String.class) {
return new String(ob.toString());
} else if ((toType == Byte.class) || (toType == byte.class)) {
return new Byte(ob.toString());
} else if ((toType == Character.class) || (toType == char.class)) {
return new Character(ob.toString().charAt(0));
} else if ((toType == Short.class) || (toType == short.class)) {
return new Short(ob.toString());
} else if ((toType == Integer.class) || (toType == int.class)) {
return new Integer(ob.toString());
} else if ((toType == Long.class) || (toType == long.class)) {
return new Long(ob.toString());
} else if ((toType == Double.class) || (toType == double.class)) {
return new Double(ob.toString());
} else if ((toType == Float.class) || (toType == float.class)) {
return new Float(ob.toString());
} else if ((toType == Boolean.class) || (toType == boolean.class)) {
return new Boolean(ob.toString().equals("true")
|| ob.toString().equals(true + "") || ob.toString().equals("1")
|| ob.toString().equals("on") || ob.toString().equals("yes"));
} else if (toType == Class.class) {
return Class.forName(ob.toString());
}
return null;
}
}
class Opts {
public static final int DEFAULT_MAX_WIDTH = 80;
public static final int DEFAULT_MAX_HEIGHT = 80;
public static final String PROPERTY_PREFIX = "sqlline.";
public static final String PROPERTY_NAME_EXIT = PROPERTY_PREFIX
+ "system.exit";
private boolean autosave = false;
private boolean silent = false;
private boolean color = false;
private boolean showHeader = true;
private int headerInterval = 100;
private boolean fastConnect = true;
private boolean autoCommit = true;
private boolean verbose = false;
private boolean force = false;
private boolean incremental = false;
private boolean showTime = true;
private boolean showWarnings = true;
private boolean showNestedErrs = false;
private String numberFormat = "default";
private int maxWidth = DEFAULT_MAX_WIDTH;
private int maxHeight = DEFAULT_MAX_HEIGHT;
private int maxColumnWidth = 15;
private int rowLimit = 0;
private int timeout = -1;
private String isolation = "TRANSACTION_REPEATABLE_READ";
private String outputFormat = "table";
private boolean trimScripts = true;
private File rcFile = new File(saveDir(), "sqlline.properties");
private String historyFile = new File(saveDir(), "history")
.getAbsolutePath();
private String runFile;
public Opts(Properties props) {
loadProperties(props);
}
public String[] possibleSettingValues() {
List vals = new LinkedList();
vals.addAll(Arrays.asList(new String[] { "yes", "no", }));
return (String[]) vals.toArray(new String[vals.size()]);
}
/**
* The save directory if HOME/.sqlline/ on UNIX, and HOME/sqlline/ on
* Windows.
*/
public File saveDir() {
String dir = System.getProperty("sqlline.rcfile");
if ((dir != null) && (dir.length() > 0)) {
return new File(dir);
}
File f = new File(System.getProperty("user.home"), ((System
.getProperty("os.name").toLowerCase().indexOf("windows") != -1) ? ""
: ".") + "sqlline").getAbsoluteFile();
try {
f.mkdirs();
} catch (Exception e) {
}
return f;
}
public void save() throws IOException {
OutputStream out = new FileOutputStream(rcFile);
save(out);
out.close();
}
public void save(OutputStream out) throws IOException {
try {
Properties props = toProperties();
// don't save maxwidth: it is automatically set based on
// the terminal configuration
props.remove(PROPERTY_PREFIX + "maxwidth");
props.store(out, getApplicationTitle());
} catch (Exception e) {
handleException(e);
}
}
String[] propertyNames() throws IllegalAccessException,
InvocationTargetException {
TreeSet names = new TreeSet();
// get all the values from getXXX methods
Method[] m = getClass().getDeclaredMethods();
for (int i = 0; (m != null) && (i < m.length); i++) {
if (!(m[i].getName().startsWith("get"))) {
continue;
}
if (m[i].getParameterTypes().length != 0) {
continue;
}
String propName = m[i].getName().substring(3).toLowerCase();
if (propName.equals("run")) {
// Not a real property
continue;
}
names.add(propName);
}
return (String[]) names.toArray(new String[names.size()]);
}
public Properties toProperties() throws IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Properties props = new Properties();
String[] names = propertyNames();
for (int i = 0; (names != null) && (i < names.length); i++) {
props.setProperty(PROPERTY_PREFIX + names[i],
Reflector.invoke(this, "get" + names[i], EMPTY_OBJ_ARRAY)
.toString());
}
debug("properties: " + props.toString());
return props;
}
public void load() throws IOException {
InputStream in = new FileInputStream(rcFile);
load(in);
in.close();
}
public void load(InputStream fin) throws IOException {
Properties p = new Properties();
p.load(fin);
loadProperties(p);
}
public void loadProperties(Properties props) {
for (Iterator i = props.keySet().iterator(); i.hasNext();) {
String key = i.next().toString();
if (key.equals(PROPERTY_NAME_EXIT)) {
// fix for sf.net bug 879422
continue;
}
if (key.startsWith(PROPERTY_PREFIX)) {
set(key.substring(PROPERTY_PREFIX.length()), props.getProperty(key));
}
}
}
public void set(String key, String value) {
set(key, value, false);
}
public boolean set(String key, String value, boolean quiet) {
try {
Reflector.invoke(this, "set" + key, new Object[]{value});
return true;
} catch (Exception e) {
if (!quiet) {
error(loc("error-setting", new Object[] { key, e }));
}
return false;
}
}
public void setFastConnect(boolean fastConnect) {
this.fastConnect = fastConnect;
}
public boolean getFastConnect() {
return this.fastConnect;
}
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
public boolean getAutoCommit() {
return this.autoCommit;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public boolean getVerbose() {
return this.verbose;
}
public void setShowTime(boolean showTime) {
this.showTime = showTime;
}
public boolean getShowTime() {
return this.showTime;
}
public void setShowWarnings(boolean showWarnings) {
this.showWarnings = showWarnings;
}
public boolean getShowWarnings() {
return this.showWarnings;
}
public void setShowNestedErrs(boolean showNestedErrs) {
this.showNestedErrs = showNestedErrs;
}
public boolean getShowNestedErrs() {
return this.showNestedErrs;
}
public void setNumberFormat(String numberFormat) {
this.numberFormat = numberFormat;
}
public String getNumberFormat() {
return this.numberFormat;
}
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
}
public int getMaxWidth() {
return this.maxWidth;
}
public void setMaxColumnWidth(int maxColumnWidth) {
this.maxColumnWidth = maxColumnWidth;
}
public int getMaxColumnWidth() {
return this.maxColumnWidth;
}
public void setRowLimit(int rowLimit) {
this.rowLimit = rowLimit;
}
public int getRowLimit() {
return this.rowLimit;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return this.timeout;
}
public void setIsolation(String isolation) {
this.isolation = isolation;
}
public String getIsolation() {
return this.isolation;
}
public void setHistoryFile(String historyFile) {
this.historyFile = historyFile;
}
public String getHistoryFile() {
return this.historyFile;
}
public void setColor(boolean color) {
this.color = color;
}
public boolean getColor() {
return this.color;
}
public void setShowHeader(boolean showHeader) {
this.showHeader = showHeader;
}
public boolean getShowHeader() {
return this.showHeader;
}
public void setHeaderInterval(int headerInterval) {
this.headerInterval = headerInterval;
}
public int getHeaderInterval() {
return this.headerInterval;
}
public void setForce(boolean force) {
this.force = force;
}
public boolean getForce() {
return this.force;
}
public void setIncremental(boolean incremental) {
this.incremental = incremental;
}
public boolean getIncremental() {
return this.incremental;
}
public void setSilent(boolean silent) {
this.silent = silent;
}
public boolean getSilent() {
return this.silent;
}
public void setAutosave(boolean autosave) {
this.autosave = autosave;
}
public boolean getAutosave() {
return this.autosave;
}
public void setOutputFormat(String outputFormat) {
this.outputFormat = outputFormat;
}
public String getOutputFormat() {
return this.outputFormat;
}
public void setTrimScripts(boolean trimScripts) {
this.trimScripts = trimScripts;
}
public boolean getTrimScripts() {
return this.trimScripts;
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
public int getMaxHeight() {
return this.maxHeight;
}
public void setRun(String runFile) {
this.runFile = runFile;
}
public String getRun() {
return this.runFile;
}
}
// ~ Inner Interfaces -------------------------------------------------------
interface OutputFormat {
int print(Rows rows);
}
// /////////////////////////////
// Console interaction classes
// /////////////////////////////
/**
* A buffer that can output segments using ANSI color.
*/
final static class ColorBuffer implements Comparable {
private static final ColorAttr BOLD = new ColorAttr("\033[1m");
private static final ColorAttr NORMAL = new ColorAttr("\033[m");
private static final ColorAttr REVERS = new ColorAttr("\033[7m");
private static final ColorAttr LINED = new ColorAttr("\033[4m");
private static final ColorAttr GREY = new ColorAttr("\033[1;30m");
private static final ColorAttr RED = new ColorAttr("\033[1;31m");
private static final ColorAttr GREEN = new ColorAttr("\033[1;32m");
private static final ColorAttr BLUE = new ColorAttr("\033[1;34m");
private static final ColorAttr CYAN = new ColorAttr("\033[1;36m");
private static final ColorAttr YELLOW = new ColorAttr("\033[1;33m");
private static final ColorAttr MAGENTA = new ColorAttr("\033[1;35m");
private static final ColorAttr INVISIBLE = new ColorAttr("\033[8m");
private final List parts = new LinkedList();
private final boolean useColor;
public ColorBuffer(boolean useColor) {
this.useColor = useColor;
append("");
}
public ColorBuffer(String str, boolean useColor) {
this.useColor = useColor;
append(str);
}
/**
* Pad the specified String with spaces to the indicated length
*
* @param str
* the String to pad
* @param len
* the length we want the return String to be
*
* @return the passed in String with spaces appended until the length
* matches the specified length.
*/
ColorBuffer pad(ColorBuffer str, int len) {
int n = str.getVisibleLength();
while (n < len) {
str.append(" ");
n++;
}
return append(str);
}
ColorBuffer center(String str, int len) {
StringBuffer buf = new StringBuffer(str);
while (buf.length() < len) {
buf.append(" ");
if (buf.length() < len) {
buf.insert(0, " ");
}
}
return append(buf.toString());
}
ColorBuffer pad(String str, int len) {
if (str == null) {
str = "";
}
return pad(new ColorBuffer(str, false), len);
}
public String getColor() {
return getBuffer(useColor);
}
public String getMono() {
return getBuffer(false);
}
String getBuffer(boolean color) {
StringBuffer buf = new StringBuffer();
for (Iterator i = parts.iterator(); i.hasNext();) {
Object next = i.next();
if (!color && (next instanceof ColorAttr)) {
continue;
}
buf.append(next.toString());
}
return buf.toString();
}
/**
* Truncate the ColorBuffer to the specified length and return the new
* ColorBuffer. Any open color tags will be closed.
*/
public ColorBuffer truncate(int len) {
ColorBuffer cbuff = new ColorBuffer(useColor);
ColorAttr lastAttr = null;
for (Iterator i = parts.iterator(); (cbuff.getVisibleLength() < len)
&& i.hasNext();) {
Object next = i.next();
if (next instanceof ColorAttr) {
lastAttr = (ColorAttr) next;
cbuff.append((ColorAttr) next);
continue;
}
String val = next.toString();
if ((cbuff.getVisibleLength() + val.length()) > len) {
int partLen = len - cbuff.getVisibleLength();
val = val.substring(0, partLen);
}
cbuff.append(val);
}
// close off the buffer with a normal tag
if ((lastAttr != null) && (lastAttr != NORMAL)) {
cbuff.append(NORMAL);
}
return cbuff;
}
public String toString() {
return getColor();
}
public ColorBuffer append(String str) {
parts.add(str);
return this;
}
public ColorBuffer append(ColorBuffer buf) {
parts.addAll(buf.parts);
return this;
}
public ColorBuffer append(ColorAttr attr) {
parts.add(attr);
return this;
}
public int getVisibleLength() {
return getMono().length();
}
public ColorBuffer append(ColorAttr attr, String val) {
parts.add(attr);
parts.add(val);
parts.add(NORMAL);
return this;
}
public ColorBuffer bold(String str) {
return append(BOLD, str);
}
public ColorBuffer lined(String str) {
return append(LINED, str);
}
public ColorBuffer grey(String str) {
return append(GREY, str);
}
public ColorBuffer red(String str) {
return append(RED, str);
}
public ColorBuffer blue(String str) {
return append(BLUE, str);
}
public ColorBuffer green(String str) {
return append(GREEN, str);
}
public ColorBuffer cyan(String str) {
return append(CYAN, str);
}
public ColorBuffer yellow(String str) {
return append(YELLOW, str);
}
public ColorBuffer magenta(String str) {
return append(MAGENTA, str);
}
public int compareTo(Object other) {
return getMono().compareTo(((ColorBuffer) other).getMono());
}
private static class ColorAttr {
private final String attr;
public ColorAttr(String attr) {
this.attr = attr;
}
public String toString() {
return attr;
}
}
}
/**
* OutputFormat for a pretty, table-like format.
*/
class TableOutputFormat implements OutputFormat {
public int print(Rows rows) {
int index = 0;
ColorBuffer header = null;
ColorBuffer headerCols = null;
final int width = opts.getMaxWidth() - 4;
// normalize the columns sizes
rows.normalizeWidths();
for (; rows.hasNext();) {
Rows.Row row = (Rows.Row) rows.next();
ColorBuffer cbuf = getOutputString(rows, row);
cbuf = cbuf.truncate(width);
if (index == 0) {
StringBuffer h = new StringBuffer();
for (int j = 0; j < row.sizes.length; j++) {
for (int k = 0; k < row.sizes[j]; k++) {
h.append('-');
}
h.append("-+-");
}
headerCols = cbuf;
header = color().green(h.toString()).truncate(
headerCols.getVisibleLength());
}
if ((index == 0)
|| ((opts.getHeaderInterval() > 0)
&& ((index % opts.getHeaderInterval()) == 0) && opts
.getShowHeader())) {
printRow(header, true);
printRow(headerCols, false);
printRow(header, true);
}
if (index != 0) { // don't output the header twice
printRow(cbuf, false);
}
index++;
}
if ((header != null) && opts.getShowHeader()) {
printRow(header, true);
}
return index - 1;
}
void printRow(ColorBuffer cbuff, boolean header) {
if (header) {
output(color().green("+-").append(cbuff).green("-+"));
} else {
output(color().green("| ").append(cbuff).green(" |"));
}
}
public ColorBuffer getOutputString(Rows rows, Rows.Row row) {
return getOutputString(rows, row, " | ");
}
ColorBuffer getOutputString(Rows rows, Rows.Row row, String delim) {
ColorBuffer buf = color();
for (int i = 0; i < row.values.length; i++) {
if (buf.getVisibleLength() > 0) {
buf.green(delim);
}
ColorBuffer v;
if (row.isMeta) {
v = color().center(row.values[i], row.sizes[i]);
if (rows.isPrimaryKey(i)) {
buf.cyan(v.getMono());
} else {
buf.bold(v.getMono());
}
} else {
v = color().pad(row.values[i], row.sizes[i]);
if (rows.isPrimaryKey(i)) {
buf.cyan(v.getMono());
} else {
buf.append(v.getMono());
}
}
}
if (row.deleted) { // make deleted rows red
buf = color().red(buf.getMono());
} else if (row.updated) { // make updated rows blue
buf = color().blue(buf.getMono());
} else if (row.inserted) { // make new rows green
buf = color().green(buf.getMono());
}
return buf;
}
}
/**
* Abstract base class representing a set of rows to be displayed.
*/
abstract class Rows implements Iterator {
final ResultSetMetaData rsMeta;
final Boolean[] primaryKeys;
final NumberFormat numberFormat;
Rows(ResultSet rs) throws SQLException {
rsMeta = rs.getMetaData();
int count = rsMeta.getColumnCount();
primaryKeys = new Boolean[count];
if (opts.getNumberFormat().equals("default")) {
numberFormat = null;
} else {
numberFormat = new DecimalFormat(opts.getNumberFormat());
}
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Update all of the rows to have the same size, set to the maximum length
* of each column in the Rows.
*/
abstract void normalizeWidths();
/**
* Return whether the specified column (0-based index) is a primary key.
* Since this method depends on whether the JDBC driver property implements
* {@link java.sql.ResultSetMetaData#getTableName} (many do not), it is not reliable
* for all databases.
*/
boolean isPrimaryKey(int col) {
if (primaryKeys[col] != null) {
return primaryKeys[col].booleanValue();
}
try {
// this doesn't always work, since some JDBC drivers (e.g.,
// Oracle's) return a blank string from getTableName.
String table = rsMeta.getTableName(col + 1);
String column = rsMeta.getColumnName(col + 1);
if ((table == null) || (table.length() == 0) || (column == null)
|| (column.length() == 0)) {
return (primaryKeys[col] = new Boolean(false)).booleanValue();
}
ResultSet pks = meta.getPrimaryKeys(meta.getConnection()
.getCatalog(), null, table);
try {
while (pks.next()) {
if (column.equalsIgnoreCase(pks.getString("COLUMN_NAME"))) {
return (primaryKeys[col] = new Boolean(true)).booleanValue();
}
}
} finally {
pks.close();
}
return (primaryKeys[col] = new Boolean(false)).booleanValue();
} catch (SQLException sqle) {
return (primaryKeys[col] = new Boolean(false)).booleanValue();
}
}
class Row {
final String[] values;
final boolean isMeta;
private boolean deleted;
private boolean inserted;
private boolean updated;
private int[] sizes;
Row(int size) throws SQLException {
isMeta = true;
values = new String[size];
sizes = new int[size];
for (int i = 0; i < size; i++) {
values[i] = rsMeta.getColumnLabel(i + 1);
sizes[i] = (values[i] == null) ? 1 : values[i].length();
}
deleted = false;
updated = false;
inserted = false;
}
Row(int size, ResultSet rs) throws SQLException {
isMeta = false;
values = new String[size];
sizes = new int[size];
try {
deleted = rs.rowDeleted();
} catch (Throwable t) {
}
try {
updated = rs.rowUpdated();
} catch (Throwable t) {
}
try {
inserted = rs.rowInserted();
} catch (Throwable t) {
}
for (int i = 0; i < size; i++) {
if (numberFormat != null) {
Object o = rs.getObject(i + 1);
if (o == null) {
values[i] = null;
} else if (o instanceof Number) {
values[i] = numberFormat.format(o);
} else {
values[i] = o.toString();
}
} else {
values[i] = String.valueOf(rs.getObject(i + 1));
}
sizes[i] = (values[i] == null) ? 1 : values[i].length();
}
}
}
}
/**
* Rows implementation which buffers all rows in a linked list.
*/
class BufferedRows extends Rows {
private final LinkedList list;
private final Iterator iterator;
BufferedRows(ResultSet rs) throws SQLException {
super(rs);
list = new LinkedList();
int count = rsMeta.getColumnCount();
list.add(new Row(count));
while (rs.next()) {
list.add(new Row(count, rs));
}
iterator = list.iterator();
}
public boolean hasNext() {
return iterator.hasNext();
}
public Object next() {
return iterator.next();
}
void normalizeWidths() {
int[] max = null;
for (int i = 0; i < list.size(); i++) {
Row row = (Row) list.get(i);
if (max == null) {
max = new int[row.values.length];
}
for (int j = 0; j < max.length; j++) {
max[j] = Math.max(max[j], row.sizes[j] + 1);
}
}
for (int i = 0; i < list.size(); i++) {
Row row = (Row) list.get(i);
row.sizes = max;
}
}
}
}