/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): N/A. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.query; // Java 2 standard packages import java.io.Serializable; import java.sql.*; import java.util.*; // Third party packages import org.apache.log4j.Logger; import org.mulgara.util.ResultSetRow; import org.mulgara.util.MemoryResultSet; /** * ITQL answer. An answer is a set of solutions, where a solution is a mapping * of {@link Variable}s to {@link Value}s. * * @created 2001-07-31 * * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a> * * @version $Revision: 1.8 $ * * @modified $Date: 2005/01/05 04:58:20 $ * * @maintenanceAuthor $Author: newmana $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright © 2001-2003 <A href="http://www.PIsoftware.com/">Plugged In * Software Pty Ltd</A> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public class AnswerImpl extends AbstractAnswer implements Answer, Serializable { /** * Allow newer compiled version of the stub to operate when changes * have not occurred with the class. * NOTE : update this serialVersionUID when a method or a public member is * deleted. */ static final long serialVersionUID = -8022357347937695884L; /** Description of the Field */ private final static MemoryResultSet ZERO; /** Description of the Field */ public final static Answer EMPTY; /** Logger. This is named after the class. */ private final static Logger logger = Logger.getLogger(AnswerImpl.class); static { try { ZERO = new MemoryResultSet(new String[] {}); EMPTY = new AnswerImpl(ZERO); } catch (TuplesException e) { throw new ExceptionInInitializerError(e); } catch (SQLException e) { throw new ExceptionInInitializerError(e); } } /** The columns of the result set. */ public Variable[] variables; /** The wrapped {@link ResultSet}. */ private ResultSet resultSet; /** * Copy constructor. TODO: when Answer becomes immutable, we won't need * by-value copies * * @param answer the answer to duplicate; <code>null</code> value disallowed * @throws IllegalArgumentException if <var>answer</var> is <code>null</code> */ public AnswerImpl(Answer answer) throws TuplesException { // Validate "answer" parameter if (answer == null) throw new IllegalArgumentException("Null \"answer\" parameter"); logger.debug("Creating AnswerImpl "); // Copy the variables variables = answer.getVariables(); // Convert variables to column name array String[] columnNames = new String[variables.length]; for (int i=0; i<variables.length; i++) columnNames[i] = variables[i].getName(); // Copy the content if (logger.isDebugEnabled()) logger.debug("Adding "+answer.getRowCount()+" rows"); try { resultSet = new MemoryResultSet(columnNames); answer.beforeFirst(); logger.debug("Reset source / Iterating Answer"); while (answer.next()) { logger.debug("Creating a row"); Object[] columnValues = new Object[answer.getNumberOfVariables()]; logger.debug("Populating a row"); try { for (int i = 0; i < columnValues.length; i++) { columnValues[i] = answer.getObject(i); if (columnValues[i] instanceof Answer && !(columnValues[i] instanceof Serializable)) { Answer ans = (Answer)columnValues[i]; columnValues[i] = new AnswerImpl(ans); try { ans.close(); } catch (TuplesException et) { logger.error("TuplesException thrown in AnswerImpl.close()", et); throw et; } catch (RuntimeException rt) { logger.error("RuntimeException thrown in AnswerImpl.close()", rt); throw rt; } } } } catch (RuntimeException re) { logger.error("Runtime Exception thrown in loop", re); throw re; } logger.debug("Adding a row"); ((MemoryResultSet)resultSet).addRow(new ResultSetRow(columnNames, columnValues)); } logger.debug("Finished iterating answer"); } catch (SQLException e) { logger.warn("Failed to copy content", e); throw new TuplesException("Couldn't copy answer", e); } if (logger.isDebugEnabled()) logger.warn("Completed construction of " + getRowCount() + " rows"); } /** * Empty constructor. This produces an answer that explicitly binds 0 results * to the specified variables. * * @param variableList a list of {@link Variable}s * @throws IllegalArgumentException if <var>variableList</var> is <code>null</code> */ public AnswerImpl(List<Variable> variableList) { String[] columns = new String[variableList.size()]; int j = 0; for (Iterator<Variable> i = variableList.iterator(); i.hasNext(); j++) { columns[j++] = i.next().getName(); } try { resultSet = new MemoryResultSet(columns); } catch (SQLException e) { throw new Error("Inexplicable constructor failure", e); } } /** * Construct an answer from an SQL result set. * * @param resultSet PARAMETER TO DO * @throws TuplesException EXCEPTION TO DO */ public AnswerImpl(ResultSet resultSet) throws TuplesException { if (resultSet == null) throw new IllegalArgumentException("Null \"resultSet\" parameter"); try { this.variables = resultSetToVariables(resultSet); // this.resultSet = new MemoryResultSet(resultSet); this.resultSet = resultSet; } catch (SQLException e) { throw new TuplesException("Couldn't create answer", e); } } /** * The default constructor produces an answer with zero rows and zero columns, * the algebraic zero for the append/join field. * */ protected AnswerImpl() { variables = new Variable[] {}; resultSet = ZERO; } /** * @deprecated The internal result set shouldn't really be exposed */ public ResultSet getResultSet() { return resultSet; } // // Methods implementing the Answer interface // public long getRowUpperBound() throws TuplesException { return getRowCount(); } public long getRowExpectedCount() throws TuplesException { return getRowCount(); } public int getRowCardinality() throws TuplesException { switch ((int)getRowCount()) { case 0: return Cursor.ZERO; case 1: return Cursor.ONE; default: return Cursor.MANY; } } /* (non-Javadoc) * @see org.mulgara.query.Cursor#isEmpty() */ public boolean isEmpty() throws TuplesException { return getRowCardinality() == Cursor.ZERO; } /** * How many solution terms does this answer comprise? * * @return the number of minterms (rows) in this sum of products * @throws TuplesException if the number of rows cannot be determined */ public long getRowCount() throws TuplesException { try { int row = resultSet.getRow(); try { resultSet.last(); return resultSet.getRow(); } finally { if (row == 0) { try { resultSet.beforeFirst(); } catch (SQLException e) { logger.warn("Couldn't reset cursor to before first row", e); } } else { if (!resultSet.absolute(row)) { logger.warn("Couldn't reset cursor to row " + row); } } } } catch (SQLException e) { throw new TuplesException("Couldn't determine solution count", e); } } /** * Gets the Object attribute of the AnswerImpl object * * @param column PARAMETER TO DO * @return The Object value * @throws TuplesException EXCEPTION TO DO */ public Object getObject(int column) throws TuplesException { try { return resultSet.getObject(column+1); } catch (SQLException e) { throw new TuplesException("Couldn't read field", e); } } /** * Gets the Object attribute of the AnswerImpl object * * @param columnName PARAMETER TO DO * @return The Object value * @throws TuplesException EXCEPTION TO DO */ public Object getObject(String columnName) throws TuplesException { try { return resultSet.getObject(columnName); } catch (SQLException e) { throw new TuplesException("Couldn't read field: " + columnName, e); } } /** * Gets the variables name of the answer object - starting at 0. * * @param column the column number columns start at 0. * @return the {@link Variable}s bound within this answer. * @throws TuplesException if there was an error accessing the variable. */ public Variable getVariable(int column) throws TuplesException { try { return new Variable(resultSet.getMetaData().getColumnName(column + 1)); } catch (SQLException e) { throw new TuplesException("Couldn't get variable for column " + (column + 1), e); } } /** * Returns a single varieable bound to the column - starting at 1. * * @return the {@link Variable}s bound within this answer. */ public Variable[] getVariables() { return variables; } /** * Returns the number of variables in a tuples. * * @return the number of variables in a tuples. */ public int getNumberOfVariables() { int noVars = 0; if (variables != null) noVars = variables.length; return noVars; } /** * Tests whether this is a unit-valued answer. A unit answer appended to * something yields the unit answer. A unit answer joined to something yields * the same something. Notionally, the unit answer has zero columns and one * row. * * @return The Unconstrained value * @throws TuplesException EXCEPTION TO DO */ public boolean isUnconstrained() throws TuplesException { return (getVariables().length == 0) && (getRowCardinality() > Cursor.ZERO); } // // Methods implementing the Tuples interface // /** * Gets the ColumnIndex attribute of the AnswerImpl object * * @param column PARAMETER TO DO * @return The ColumnIndex value * @throws TuplesException EXCEPTION TO DO */ public int getColumnIndex(Variable column) throws TuplesException { // Validate "column" parameter if (column == null) throw new IllegalArgumentException("Null \"column\" parameter"); // Look for the requested variable in the "variables" array for (int i = 0; i < variables.length; i++) { if (column.equals(variables[i])) return i; } // Couldn't find the requested variable throw new TuplesException("No such column " + column); } /** * Reset the cursor. Calling {@link #next()} after calling this method will move to the first row. * * @throws TuplesException There was an error accessing the underlying data. */ public void beforeFirst() throws TuplesException { try { resultSet.beforeFirst(); } catch (SQLException e) { throw new TuplesException("Couldn't rewind solution", e); } } /** * Advance to the next term in the solution. * * @return <code>false<code> if there was no further term to advance to. * @throws TuplesException EXCEPTION TO DO */ public boolean next() throws TuplesException { try { return resultSet.next(); } catch (SQLException e) { throw new TuplesException("Couldn't advance cursor", e); } } // // Methods overriding the Object superclass // /** * Equality. * * @param object the object to compare against for equality, possibly <code>null</code> * @return whether <var>object</var> is equal to this instance; the order of * both columns and rows is significant */ public boolean equals(Object object) { // Gotta be non-null and of matching type if ((object != null) && (object instanceof Answer)) { try { return AnswerOperations.equal(this, (Answer) object); } catch (TuplesException e) { logger.fatal("Couldn't test equality of answers", e); } } return false; } /** * Added to match {@link #equals(Object)}. */ public int hashCode() { return super.hashCode(); } /** * Generate a legible representation of the answer. * * @return a string representation of the results. */ public String toString() { return resultSet.toString(); } /** * Creates a copy of this object and its resources. * * @return A new, independent copy of this object. */ public Object clone() { try { AnswerImpl a = (AnswerImpl)super.clone(); a.resultSet = new MemoryResultSet(resultSet); return a; } catch (SQLException e) { throw new RuntimeException(e); } } /** * Release all resources associated with this object */ public void close() { // resultSet = null; // variables = null; } /** * Convert the column names of a result set to an array of Variables. * * @param resultSet The ResultSet to convert. * @return An array of variables corresponding to the columns resultSet. * @throws SQLException An error accessing the ResultSet metadata. */ private Variable[] resultSetToVariables(ResultSet resultSet) throws SQLException { ResultSetMetaData rsmd = resultSet.getMetaData(); Variable[] variables = new Variable[rsmd.getColumnCount()]; for (int i = 0; i < rsmd.getColumnCount(); i++) { variables[i] = new Variable(rsmd.getColumnName(i + 1)); } return variables; } }