/* * * SchemaCrawler * http://sourceforge.net/projects/schemacrawler * Copyright (c) 2000-2013, 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.crawl; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import sf.util.Utility; /** * A wrapper around a JDBC resultset obtained from a database metadata * call. This allows type-safe methods to obtain boolean, integer and * string data, while abstracting away the quirks of the JDBC metadata * API. * * @author Sualeh Fatehi */ final class MetadataResultSet implements AutoCloseable { private static final Logger LOGGER = Logger.getLogger(MetadataResultSet.class .getName()); private static final int FETCHSIZE = 20; private final ResultSet results; private final List<String> resultSetColumns; private Set<String> readColumns; MetadataResultSet(final ResultSet resultSet) throws SQLException { if (resultSet == null) { throw new IllegalArgumentException("Cannot use null results"); } results = resultSet; try { results.setFetchSize(FETCHSIZE); } catch (final NullPointerException | SQLException e) { LOGGER.log(Level.WARNING, "Could not set fetch size", e); } final List<String> resultSetColumns = new ArrayList<>(); try { final ResultSetMetaData rsMetaData = resultSet.getMetaData(); for (int i = 0; i < rsMetaData.getColumnCount(); i++) { String columnName; columnName = rsMetaData.getColumnLabel(i + 1); if (Utility.isBlank(columnName)) { columnName = rsMetaData.getColumnName(i + 1); } resultSetColumns.add(columnName.toUpperCase()); } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not get columns list"); } this.resultSetColumns = Collections.unmodifiableList(resultSetColumns); readColumns = new HashSet<>(); } /** * Releases this <code>ResultSet</code> object's database and JDBC * resources immediately instead of waiting for this to happen when it * is automatically closed. * * @throws SQLException * On an exception */ @Override public void close() throws SQLException { results.close(); } /** * Gets unread (and therefore unmapped) columns from the database * metadata resultset, and makes them available as addiiotnal * attributes. * * @return Map of additional attributes to the database object */ Map<String, Object> getAttributes() { final Map<String, Object> attributes = new HashMap<>(); for (final String columnName: resultSetColumns) { if (!readColumns.contains(columnName)) { try { final Object value = results.getObject(columnName); attributes.put(columnName, value); } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read value for column " + columnName, e); } } } return attributes; } /** * Checks if the value of a column from the result set evaluates to * true. * * @param columnName * Column name to check * @return Whether the string evaluates to true */ boolean getBoolean(final String columnName) { boolean value = false; if (useColumn(columnName)) { try { final Object booleanValue = results.getObject(columnName); final String stringBooleanValue; if (results.wasNull() || booleanValue == null) { stringBooleanValue = null; } else { stringBooleanValue = String.valueOf(booleanValue); } if (!Utility.isBlank(stringBooleanValue)) { try { final int booleanInt = Integer.parseInt(stringBooleanValue); value = booleanInt != 0; } catch (final NumberFormatException e) { value = stringBooleanValue.equalsIgnoreCase("YES") || Boolean.valueOf(stringBooleanValue).booleanValue(); } } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read boolean value for column " + columnName, e); } } return value; } /** * Reads the value of a column from the result set as an enum. * * @param columnName * Column name * @param defaultValue * Default enum value to return * @return Enum value of the column, or the default if not available */ <E extends Enum<E>> E getEnum(final String columnName, final E defaultValue) { final String value = getString(columnName); E enumValue; if (value == null || defaultValue == null) { enumValue = defaultValue; } else { try { enumValue = (E) Enum.valueOf(defaultValue.getClass(), value.toLowerCase(Locale.ENGLISH)); } catch (final Exception e) { enumValue = defaultValue; } } return enumValue; } /** * Reads the value of a column from the result set as an integer. If * the value was null, returns the default. * * @param columnName * Column name * @param defaultValue * Default value * @return Integer value of the column, or the default if not * available */ int getInt(final String columnName, final int defaultValue) { int value = defaultValue; if (useColumn(columnName)) { try { value = results.getInt(columnName); if (results.wasNull()) { value = defaultValue; } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read integer value for column " + columnName, e); } } return value; } /** * Reads the value of a column from the result set as a long. If the * value was null, returns the default. * * @param columnName * Column name * @param defaultValue * Default value * @return Long value of the column, or the default if not available */ long getLong(final String columnName, final long defaultValue) { long value = defaultValue; if (useColumn(columnName)) { try { value = results.getLong(columnName); if (results.wasNull()) { value = defaultValue; } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read long value for column " + columnName, e); } } return value; } /** * Reads the value of a column from the result set as a short. If the * value was null, returns the default. * * @param columnName * Column name * @param defaultValue * Default value * @return Short value of the column, or the default if not available */ short getShort(final String columnName, final short defaultValue) { short value = defaultValue; if (useColumn(columnName)) { try { value = results.getShort(columnName); if (results.wasNull()) { value = defaultValue; } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read short value for column " + columnName, e); } } return value; } /** * Reads the value of a column from the result set as a string. * * @param columnName * Column name * @return String value of the column, or null if not available */ String getString(final String columnName) { String value = null; if (useColumn(columnName)) { try { value = results.getString(columnName); if (results.wasNull()) { value = null; } } catch (final SQLException e) { LOGGER.log(Level.WARNING, "Could not read string value for column " + columnName, e); } } return value; } /** * Moves the cursor down one row from its current position. A * <code>ResultSet</code> cursor is initially positioned before the * first row; the first call to the method <code>next</code> makes the * first row the current row; the second call makes the second row the * current row, and so on. * * @return <code>true</code> if the new current row is valid; * <code>false</code> if there are no more rows * @throws SQLException * On a database access error */ boolean next() throws SQLException { readColumns = new HashSet<>(); return results.next(); } private boolean useColumn(final String columnName) { final boolean useColumn = columnName != null && resultSetColumns.contains(columnName); if (useColumn) { readColumns.add(columnName); } return useColumn; } }