net.ontopia.topicmaps.query.toma.impl.basic.ResultSet.java Source code

Java tutorial

Introduction

Here is the source code for net.ontopia.topicmaps.query.toma.impl.basic.ResultSet.java

Source

/**
 * Copyright (C) 2009 Space Applications Services
 *   <thomas.neidhart@spaceapplications.com>
 *
 * This file is part of the Ontopia project.
 *
 * 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.
 */
package net.ontopia.topicmaps.query.toma.impl.basic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.commons.collections.Bag;
import org.apache.commons.collections.bag.HashBag;

import net.ontopia.utils.CompactHashSet;

/**
 * INTERNAL: This is a very simple implementation of a table, which is used to
 * represent matching result for TOMA queries.
 * <p>
 * The underlying data-structure is a {@link Bag}, which allows transparent
 * access to unique/duplicate rows.
 * </p>
 */
public class ResultSet implements Iterable<Row> {

    private Vector<String> columns;
    private Bag rows;
    private boolean unique;

    /**
     * Create an empty {@link ResultSet}, i.e. with zero columns.
     */
    public ResultSet() {
        this(0, false);
    }

    /**
     * Create a new {@link ResultSet} with the given number of columns.
     * 
     * @param cols the number of columns.
     * @param unique indicates whether this {@link ResultSet} should only contain
     *          unique rows, or allows duplicates.
     */
    public ResultSet(int cols, boolean unique) {
        columns = new Vector<String>(cols);
        columns.setSize(cols);
        rows = new HashBag();
        this.unique = unique;
    }

    /**
     * Create a new {@link ResultSet} that is based on the column definition from
     * one or more other {@link ResultSet}'s. The new {@link ResultSet} may
     * contain duplicate rows.
     * 
     * @param others the {@link ResultSet}'s
     */
    public ResultSet(ResultSet... others) {
        int cols = 0;
        for (ResultSet rs : others) {
            cols += rs.getColumnCount();
        }
        columns = new Vector<String>(cols);
        columns.setSize(cols);

        int i = 0;
        for (ResultSet rs : others) {
            for (int j = 0; j < rs.getColumnCount(); j++, i++) {
                setColumnName(i, rs.getColumnName(j));
            }
        }

        this.rows = new HashBag();
        this.unique = false;
    }

    /**
     * Returns an unmodifiable {@link List} of the column definitions.
     * 
     * @return an unmodifiable {@link List} of the columns.
     */
    public List<String> getColumnDefinitions() {
        return Collections.unmodifiableList(columns);
    }

    /**
     * Returns the name of the column at the given index.
     * 
     * @param index the column index.
     * @return the name of the column.
     * @throws IndexOutOfBoundsException if the index is outside the range of the
     *           column definition (e.g. index < 0 or index >= getColumnCount()).
     */
    public String getColumnName(int index) throws IndexOutOfBoundsException {
        if (index < 0 || index >= columns.size()) {
            throw new IndexOutOfBoundsException("No column available for index '" + index + "'");
        }
        return columns.get(index);
    }

    /**
     * Set the name of a specified column.
     * 
     * @param index the index of the column.
     * @param name the new name of the column.
     */
    public void setColumnName(int index, String name) {
        columns.set(index, name);
    }

    /**
     * Returns whether a column with the given name exists within this
     * {@link ResultSet}.
     * 
     * @param name the name of the column to look for.
     * @return true if a column with the given name exists; false otherwise.
     */
    public boolean containsColumn(String name) {
        return (getColumnIndex(name) > -1);
    }

    /**
     * Returns the number of columns contained in this {@link ResultSet}.
     * 
     * @return the number of columns.
     */
    public int getColumnCount() {
        return columns.size();
    }

    /**
     * Returns the index of the column with the given name.
     * 
     * @param name the name of the column to look for.
     * @return the index of the column, or -1 if no such column exists.
     */
    public int getColumnIndex(String name) {
        int idx = 0;
        for (String column : columns) {
            if (column.equals(name)) {
                return idx;
            }
            idx++;
        }
        return -1;
    }

    /**
     * Returns whether this {@link ResultSet} contains unique or duplicate rows.
     * 
     * @return true if this {@link ResultSet} only contains unique rows; false
     *         otherwise.
     */
    public boolean isUnique() {
        return unique;
    }

    /**
     * Set whether this {@link ResultSet} only allows unique rows, or can also
     * store duplicate ones.
     * 
     * @param unique if this {@link ResultSet} should only store unique rows, use
     *          true; false otherwise.
     */
    public void setUnique(boolean unique) {
        this.unique = unique;
    }

    /**
     * Returns a {@link List} of all shared columns with the given
     * {@link ResultSet}.
     * 
     * @param rs the other {@link ResultSet}.
     * @return a {@link List} containing the shared columns.
     */
    public List<String> getSharedColumns(ResultSet rs) {
        List<String> sharedCols = new ArrayList<String>();

        List<String> vars = getBoundVariables();
        for (String var : vars) {
            if (rs.containsColumn(var)) {
                sharedCols.add(var);
            }
        }

        return sharedCols;
    }

    /**
     * Returns a {@link List} of variables that are present in this
     * {@link ResultSet}.
     * 
     * @return a {@link List} of variables in this {@link ResultSet}.
     */
    public List<String> getBoundVariables() {
        List<String> variables = new ArrayList<String>();
        for (String col : columns) {
            if (col.startsWith("$") && !col.contains(".")) {
                variables.add(col);
            }
        }
        return variables;
    }

    /**
     * Returns the number of rows that are stored in this {@link ResultSet}.
     * 
     * @return the number of rows.
     */
    public int getRowCount() {
        if (unique) {
            return rows.uniqueSet().size();
        } else {
            return rows.size();
        }
    }

    /**
     * Returns the index of the last column in this {@link ResultSet}. This is a
     * convenience method, and returns the same result as:
     * 
     * <pre>
     *   getColumnCount() - 1;
     * </pre>
     * 
     * @return the index of the last column.
     */
    public int getLastIndex() {
        return columns.size() - 1;
    }

    /**
     * Adds a new column to this {@link ResultSet}. The new column is appended at
     * the end of the existing columns. Every row, that is currently stored in
     * this {@link ResultSet} is adjusted to the new column size (filled with zero
     * values).
     * 
     * @param name the name of the new column.
     */
    public void addColumn(String name) {
        columns.add(name);
        for (Object r : rows) {
            ((Row) r).addColumn();
        }
    }

    /**
     * Returns an iterator over the rows of this {@link ResultSet}.
     * 
     * @return an iterator over all rows.
     */
    @SuppressWarnings("unchecked")
    public Iterator<Row> iterator() {
        if (unique) {
            return rows.uniqueSet().iterator();
        } else {
            return rows.iterator();
        }
    }

    /**
     * Return a new {@link Row} that is based on the column definitions of this
     * {@link ResultSet}.
     * <p>
     * <b>Note</b>: The returned {@link Row} is not added to the {@link ResultSet}.
     * 
     * @return a new {@link Row} that matches the column definitions of this
     *         {@link ResultSet}.
     */
    public Row createRow() {
        Row r = new Row(getColumnCount());
        return r;
    }

    /**
     * Adds a {@link Row} to this {@link ResultSet}.
     * 
     * @param row the {@link Row} to be added.
     */
    public void addRow(Row row) {
        rows.add(row);
    }

    /**
     * Adds all rows from the other {@link ResultSet} to this one.
     * <p>
     * <b>Note</b>: The layout of the two ResultSets has to be the same, otherwise
     * this operation will fail.
     * </p>
     * 
     * @param other the ResultSet to be added.
     */
    @SuppressWarnings("unchecked")
    public void addAll(ResultSet other) {
        rows.addAll(other.rows);
    }

    /**
     * Remove the given {@link Row} from this {@link ResultSet}.
     * 
     * @param row the {@link Row} to be removed.
     */
    public void removeRow(Row row) {
        rows.remove(row);
    }

    /**
     * Indicates whether the given {@link Row} is contained in this
     * {@link ResultSet}.
     * 
     * @param row the {@link Row} to be looked up.
     * @return true if the {@link Row} is contained in the {@link ResultSet};
     *         false otherwise.
     */
    public boolean containsRow(Row row) {
        return rows.contains(row);
    }

    /**
     * Remove all rows from this {@link ResultSet}.
     */
    public void removeAllRows() {
        rows.clear();
    }

    /**
     * Returns a unique {@link Collection} of all values in the specified column.
     *  
     * @param idx the column.
     * @return a {@link Collection} of values in that column.
     */
    @SuppressWarnings("unchecked")
    public Collection getValues(int idx) {
        Collection col = new CompactHashSet(rows.size());

        if (idx == -1)
            return col;

        for (Object r : rows.uniqueSet()) {
            Row row = (Row) r;
            Object val = row.getValue(idx);
            col.add(val);
        }

        return col;
    }

    /**
     * Returns all values in a specified column. This is a convenience method and
     * returns the same result as:
     *
     * <pre>
     *   getValues(getColumnIndex(name));
     * </pre>
     * 
     * @param column the column.
     * @return a {@link Collection} of values in that column.
     */
    @SuppressWarnings("unchecked")
    public Collection getValues(String column) {
        int idx = getColumnIndex(column);
        return getValues(idx);
    }

    /**
     * Returns all valid values from a specified column of this {@link ResultSet}. 
     * A valid value is a non-null value.
     * 
     * @param idx the column.
     * @return a {@link Collection} containing all valid values.
     */
    @SuppressWarnings("unchecked")
    public Collection getValidValues(int idx) {
        Collection col = new CompactHashSet(rows.size());

        if (idx == -1)
            return col;

        for (Object r : rows.uniqueSet()) {
            Row row = (Row) r;
            Object val = row.getValue(idx);
            if (val != null) {
                col.add(val);
            }
        }

        return col;
    }

    /**
     * Returns a new {@link ResultSet} that is the result of a merge operation of
     * the current {@link ResultSet} and the given one.
     * 
     * FIXME: this method has to be fixed, it does not produce correct results
     * for ResultSet that share more than one column.
     * 
     * @param rs the other {@link ResultSet} to be used for the merging.
     * @return a merged {@link ResultSet}.
     */
    @SuppressWarnings("unchecked")
    public ResultSet merge(ResultSet rs) {
        List<String> sharedCols = getSharedColumns(rs);
        if (sharedCols.isEmpty()) {
            return null;
        } else {
            String col = sharedCols.get(0);
            Collection vals1 = getValues(col);
            Collection vals2 = rs.getValues(col);

            vals1.addAll(vals2);
            ResultSet result = new ResultSet(1, true);
            result.setColumnName(0, col);

            for (Object o : vals1) {
                Row r = result.createRow();
                r.setLastValue(o);
                result.addRow(r);
            }

            return result;
        }
    }

    /**
     * Perform a union operation with the other {@link ResultSet}. As a result,
     * the current {@link ResultSet} will be extended with the rows from the other
     * {@link ResultSet}.
     * 
     * @param other the {@link ResultSet} that should be merged into the current
     *          {@link ResultSet}.
     * @param distinct indicates whether a distinct union operation should be
     *          performed or not.
     */
    public void union(ResultSet other, boolean distinct) {
        for (Row r : other) {
            if (!distinct || !containsRow(r)) {
                addRow(r);
            }
        }
    }

    /**
     * Perform an intersect operation with the other {@link ResultSet}. As a
     * result, the current {@link ResultSet} will be reduced to the intersection
     * of the two {@link ResultSet}'s.
     * 
     * @param other the {@link ResultSet} that should be intersected with the
     *          current {@link ResultSet}.
     */
    public void intersect(ResultSet other) {
        List<Row> toDelete = new ArrayList<Row>(other.getRowCount());
        for (Row r : this) {
            if (!other.containsRow(r)) {
                toDelete.add(r);
            }
        }
        rows.removeAll(toDelete);
    }

    /**
     * Perform an except operation with the other {@link ResultSet}. As a result,
     * the current {@link ResultSet} will be reduced with the rows from the other
     * {@link ResultSet}.
     * 
     * @param other the {@link ResultSet} that should be removed from the current
     *          {@link ResultSet}.
     */
    public void except(ResultSet other) {
        for (Row r : other) {
            removeRow(r);
        }
    }

    /**
     * Returns a {@link List} representation of this {@link ResultSet}.
     * 
     * @return a {@link List} containing all {@link Row} objects of the
     *         {@link ResultSet}.
     */
    public List<Row> getList() {
        List<Row> l = new ArrayList<Row>(getRowCount());
        for (Row r : this) {
            l.add(r);
        }
        return l;
    }
}